SPS · log · files · refs · tags · releases · people · languages · GitHub · commit
27f1327
Speed up repository indexing and only pull build tools a compile needs.
Parents
Stat
DESIGN.md | 14 +++- PACKAGE-MAINTAINER.md | 10 ++- bin/mkiso | 4 +- bin/mkpkg | 25 +++++- bin/sget | 181 +++++++++++++++++++++++++++++++++++++++-- bin/src | 176 +++++++++++++++++++++++++++++++++------ lib/common.sh | 126 +++++++++++++++++++--------- lib/deps.awk | 3 +- lib/setup/extras | 17 +++- lib/setup/sets/daily-drive.set | 5 ++ lib/setup/sets/multimedia.set | 8 +- lib/setup/sets/office.set | 8 +- man/mkpkg.1 | 8 ++ man/sget.1 | 8 ++ man/src.1 | 21 +++-- tests/test_build.sh | 8 +- tests/test_dependencies.sh | 21 ++++- tests/test_mkiso.sh | 6 +- tests/test_repository.sh | 14 ++++ tests/test_src_check.sh | 6 ++ 20 files changed, 573 insertions(+), 96 deletions(-)
Diff
diff --git a/DESIGN.md b/DESIGN.md
index 21da531..c6ffd14 100644
--- a/DESIGN.md
+++ b/DESIGN.md
@@ -116,7 +116,9 @@ Dependency lists are comma-separated. Field values may not contain tabs or
newlines. The definition digest covers the recipe content and the relative
paths, contents, and copied modes of its `files/`, `patches/`, and `hooks/`
inputs, so a changed support file invalidates the index as well as a changed
-recipe.
+recipe. Recipe-only packages hash the same two-line manifest; `src update`
+batches those file hashes and then applies that manifest, so the digest
+matches the per-package hasher.
When more than one configured repository supplies the same package, SPS picks
the highest numeric priority. Configuration order breaks equal priorities. A
@@ -314,6 +316,16 @@ the upstream version, it must bump the package release.
Runtime and build dependencies form a directed graph. `sget` computes a stable
topological order and rejects missing dependencies and cycles.
+`sget install` resolves the runtime closure first. Build dependencies are added
+only for packages that will actually be compiled (not already installed at the
+selected version, and not a usable cached archive). Well-known build tools
+already on `PATH` (`cmake`, `ninja`, `meson`, `patch`, `pkgconf`, `gperf`,
+`python`, `go`, `rust`) are used in place of those packages when they are not
+themselves a runtime requirement. That is why `fastfetch` does not install
+`cmake` or `openssl` on a live image that already has cmake.
+
+`sget depends --build` still shows the full declared build graph.
+
`sget upgrade` starts with the explicit `world` set and resolves its selected
dependency closure. `sget dependees` shows reverse dependencies. `sget why`
shows one dependency path between two selected packages.
diff --git a/PACKAGE-MAINTAINER.md b/PACKAGE-MAINTAINER.md
index ffbfd77..b2907c1 100644
--- a/PACKAGE-MAINTAINER.md
+++ b/PACKAGE-MAINTAINER.md
@@ -93,8 +93,10 @@ optional doxygen
```
Supported operators are `=`, `<`, `<=`, `>`, and `>=`. Constraints apply to
-the upstream version, not the package release. `sget install` installs build
-dependencies while building a dependency closure. `optional` is informational
+the upstream version, not the package release. `sget install` adds a package's
+build dependencies only when that package will be compiled. Well-known tools
+already on `PATH` (cmake, ninja, meson, patch, pkgconf, python, ...) satisfy a
+builddep without installing the package. `optional` is informational
and is not installed automatically. Low-level `mkpkg` and `pkin` do not resolve
dependencies.
@@ -184,7 +186,9 @@ A real `files/` directory is copied to `$SRC/files`; a real `patches/` directory
is copied to `$SRC/patches`. Entries must be ordinary directories or regular
files. Symlinks, special files, and paths containing tabs or newlines are
rejected. The repository definition digest includes support paths, contents,
-and copied modes, so run `src update` after any of them changes. Nothing is
+and copied modes, so run `src update` after any of them changes. `src update`
+batches recipe-only hashes; the digest is the same as hashing one package at
+a time. Nothing is
applied or installed automatically.
For example:
diff --git a/bin/mkiso b/bin/mkiso
index 4a3c4d8..57f5c4d 100755
--- a/bin/mkiso
+++ b/bin/mkiso
@@ -997,8 +997,8 @@ mkiso_verify_plasma()
}
# 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".
+# recipe trees on this image. An extras checklist entry with no matching
+# recipe is how live setup died at "package not found".
mkiso_verify_setup_catalog()
{
mkiso_setupdir=$SPS_LIBDIR/setup
diff --git a/bin/mkpkg b/bin/mkpkg
index a5ee01a..0171c8f 100755
--- a/bin/mkpkg
+++ b/bin/mkpkg
@@ -520,7 +520,8 @@ download_http()
download_url=$1
download_dest=$2
if command -v curl >/dev/null 2>&1; then
- curl -fL --progress-bar -o "$download_dest" "$download_url"
+ curl -fL --retry 3 --retry-delay 1 --progress-bar \
+ -o "$download_dest" "$download_url"
elif command -v wget >/dev/null 2>&1; then
wget -O "$download_dest" "$download_url"
else
@@ -725,7 +726,7 @@ write_cc_wrapper()
{
printf '%s\n' '#!/bin/sh'
write_host_ld_restore
- printf 'exec %s -isystem %s -L%s -L%s -L%s -L%s -Wl,-rpath-link,%s -Wl,-rpath-link,%s "$@"\n' \
+ printf 'exec %s -isystem %s -L%s -L%s -L%s -L%s -Wl,-rpath-link,%s -Wl,-rpath-link,%s -Wl,-rpath-link,%s -Wl,-rpath-link,%s "$@"\n' \
"$(shell_quote "$real_cc")" \
"$(shell_quote "$target_inc")" \
"$(shell_quote "$target_root/usr/lib64")" \
@@ -733,7 +734,9 @@ write_cc_wrapper()
"$(shell_quote "$target_root/lib64")" \
"$(shell_quote "$target_root/lib")" \
"$(shell_quote "$target_root/usr/lib64")" \
- "$(shell_quote "$target_root/usr/lib")"
+ "$(shell_quote "$target_root/usr/lib")" \
+ "$(shell_quote "$target_root/lib64")" \
+ "$(shell_quote "$target_root/lib")"
} >"$wrapper_path"
chmod 755 "$wrapper_path"
}
@@ -818,6 +821,22 @@ if [ -n "${SPS_ROOT:-}" ] && [ "$SPS_ROOT" != / ]; then
export CPATH C_INCLUDE_PATH CPLUS_INCLUDE_PATH LIBRARY_PATH \
SPS_HOST_LD_LIBRARY_PATH SPS_TARGET_LIBRARY_PATH LD_LIBRARY_PATH
+ # OpenSSL 3 looks up providers by a compiled-in MODULESDIR (/usr/lib64
+ # on the live disc). A just-built _ssl.so must load the target's
+ # modules, not the live ones, or CPython renames it *_failed.so.
+ if [ -d "$target_root/usr/lib64/ossl-modules" ]; then
+ OPENSSL_MODULES=$target_root/usr/lib64/ossl-modules
+ elif [ -d "$target_root/usr/lib/ossl-modules" ]; then
+ OPENSSL_MODULES=$target_root/usr/lib/ossl-modules
+ fi
+ if [ -n "${OPENSSL_MODULES:-}" ]; then
+ export OPENSSL_MODULES
+ fi
+ if [ -f "$target_root/etc/ssl/openssl.cnf" ]; then
+ OPENSSL_CONF=$target_root/etc/ssl/openssl.cnf
+ export OPENSSL_CONF
+ fi
+
CPPFLAGS="$target_cflags${CPPFLAGS:+ $CPPFLAGS}"
CFLAGS="$target_cflags${CFLAGS:+ $CFLAGS}"
CXXFLAGS="$target_cflags${CXXFLAGS:+ $CXXFLAGS}"
diff --git a/bin/sget b/bin/sget
index b865166..78437b2 100755
--- a/bin/sget
+++ b/bin/sget
@@ -269,6 +269,176 @@ sget_verify_artifact_identity()
# Set by the upgrade path.
sget_upgrade_mode=${sget_upgrade_mode:-0}
+# Build-only tools that a live disc already seeds. Skip installing the
+# package when it is not a runtime requirement and the host command exists.
+sget_host_build_tool()
+{
+ case $1 in
+ cmake) command -v cmake >/dev/null 2>&1 ;;
+ ninja) command -v ninja >/dev/null 2>&1 ;;
+ meson) command -v meson >/dev/null 2>&1 ;;
+ patch) command -v patch >/dev/null 2>&1 ;;
+ pkgconf) command -v pkg-config >/dev/null 2>&1 ||
+ command -v pkgconf >/dev/null 2>&1 ;;
+ gperf) command -v gperf >/dev/null 2>&1 ;;
+ python) command -v python3 >/dev/null 2>&1 ;;
+ go) command -v go >/dev/null 2>&1 ;;
+ rust) command -v rustc >/dev/null 2>&1 &&
+ command -v cargo >/dev/null 2>&1 ;;
+ unzip) command -v unzip >/dev/null 2>&1 ;;
+ lzip) command -v lzip >/dev/null 2>&1 ;;
+ *) return 1 ;;
+ esac
+}
+
+sget_selected_index_field()
+{
+ awk -F "$sps_tab" -v pkg="$1" -v col="$2" '
+ $1 == pkg {
+ pri = $9 + 0
+ if (!seen || pri > best) {
+ best = pri
+ val = $col
+ seen = 1
+ }
+ }
+ END { if (seen) print val }
+ ' "$sps_index"
+}
+
+sget_cache_usable()
+{
+ sget_cu_name=$1
+ sget_cu_version=$2
+ sget_cu_release=$3
+ sget_cu_arch=$4
+ sget_cu_hash=$5
+ sget_cu_base=$SPS_CACHE/packages/$sget_cu_name-$sget_cu_version-$sget_cu_release-$sget_cu_arch.pkg.tar
+ for sget_cu_candidate in "$sget_cu_base.zst" "$sget_cu_base.xz" \
+ "$sget_cu_base.gz" "$sget_cu_base"
+ do
+ [ -f "$sget_cu_candidate" ] && [ ! -L "$sget_cu_candidate" ] || continue
+ sget_verify_artifact_identity "$sget_cu_candidate" "$sget_cu_name" \
+ "$sget_cu_version" "$sget_cu_release" "$sget_cu_arch" \
+ "$sget_cu_hash" >/dev/null 2>&1 || continue
+ return 0
+ done
+ return 1
+}
+
+# True when mkpkg will run for this selected record.
+sget_will_compile()
+{
+ sget_wc_name=$1
+ sget_wc_version=$2
+ sget_wc_release=$3
+ sget_wc_arch=$4
+ sget_wc_hash=$5
+ sget_wc_cache=$sget_work/will-compile/$sget_wc_name
+ if [ -f "$sget_wc_cache" ]; then
+ read -r sget_wc_cached <"$sget_wc_cache"
+ return "$sget_wc_cached"
+ fi
+ if sget_read_installed "$sget_wc_name"; then
+ if ! sget_wc_cmp=$(sget_compare_selected "$sget_wc_version" \
+ "$sget_wc_release" "$sget_installed_version" \
+ "$sget_installed_release")
+ then
+ printf '%s\n' 0 >"$sget_wc_cache"
+ return 0
+ fi
+ if [ "$sget_wc_cmp" -eq 0 ] &&
+ [ "$sget_installed_arch" = "$sget_wc_arch" ] &&
+ [ "$sget_reinstall" -eq 0 ]; then
+ printf '%s\n' 1 >"$sget_wc_cache"
+ return 1
+ fi
+ fi
+ if sget_cache_usable "$sget_wc_name" "$sget_wc_version" \
+ "$sget_wc_release" "$sget_wc_arch" "$sget_wc_hash"
+ then
+ printf '%s\n' 1 >"$sget_wc_cache"
+ return 1
+ fi
+ printf '%s\n' 0 >"$sget_wc_cache"
+ return 0
+}
+
+# Runtime closure first. Add builddeps only for packages that will be
+# compiled, and skip well-known tools already on PATH.
+sget_plan_with_builddeps()
+{
+ sget_all_roots=$sget_roots
+ sget_round=0
+ mkdir -p "$sget_work/will-compile"
+ while :
+ do
+ sget_round=$((sget_round + 1))
+ [ "$sget_round" -lt 32 ] ||
+ sps_die "$SPS_EX_DEPENDENCY" "build-dependency expansion did not settle"
+ if ! awk -v action=plan -v roots="$sget_all_roots" \
+ -v include_build=0 -v program=sget \
+ -v installed="$(sget_installed_names)" \
+ -f "$SPS_LIBDIR/version.awk" -f "$SPS_LIBDIR/deps.awk" \
+ "$sps_index" >"$sget_work/resolution"
+ then
+ sps_die "$SPS_EX_DEPENDENCY" "cannot resolve requested packages"
+ fi
+ : >"$sget_work/new-build-roots"
+ while IFS="$sps_tab" read -r sget_pl_name sget_pl_recipe sget_pl_hash \
+ sget_pl_version sget_pl_release sget_pl_arch sget_pl_bdeps || \
+ [ -n "$sget_pl_name" ]
+ do
+ [ -n "$sget_pl_name" ] || continue
+ sget_will_compile "$sget_pl_name" "$sget_pl_version" \
+ "$sget_pl_release" "$sget_pl_arch" "$sget_pl_hash" || continue
+ [ -n "$sget_pl_bdeps" ] || continue
+ sget_pl_saved_ifs=$IFS
+ IFS=,
+ set -- $sget_pl_bdeps
+ IFS=$sget_pl_saved_ifs
+ for sget_pl_bdep
+ do
+ sget_pl_bdep=$(printf '%s' "$sget_pl_bdep" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
+ [ -n "$sget_pl_bdep" ] || continue
+ case $sget_pl_bdep in
+ *'>='*) sget_pl_bdep=${sget_pl_bdep%%>=*} ;;
+ *'<='*) sget_pl_bdep=${sget_pl_bdep%%<=*} ;;
+ *'='*) sget_pl_bdep=${sget_pl_bdep%%=*} ;;
+ *'>'*) sget_pl_bdep=${sget_pl_bdep%%>*} ;;
+ *'<'*) sget_pl_bdep=${sget_pl_bdep%%<*} ;;
+ esac
+ [ -n "$sget_pl_bdep" ] || continue
+ case ,$sget_all_roots, in
+ *,"$sget_pl_bdep",*) continue ;;
+ esac
+ if sget_host_build_tool "$sget_pl_bdep"; then
+ sget_note "using host $sget_pl_bdep to build $sget_pl_name"
+ continue
+ fi
+ printf '%s\n' "$sget_pl_bdep" >>"$sget_work/new-build-roots"
+ done
+ done <"$sget_work/resolution"
+ [ -s "$sget_work/new-build-roots" ] || return 0
+ sget_new_roots=
+ while IFS= read -r sget_pl_bdep || [ -n "$sget_pl_bdep" ]
+ do
+ [ -n "$sget_pl_bdep" ] || continue
+ case ,$sget_all_roots, in
+ *,"$sget_pl_bdep",*) continue ;;
+ esac
+ case ,$sget_new_roots, in
+ *,"$sget_pl_bdep",*) continue ;;
+ esac
+ [ -n "$sget_new_roots" ] &&
+ sget_new_roots=$sget_new_roots,$sget_pl_bdep ||
+ sget_new_roots=$sget_pl_bdep
+ done <"$sget_work/new-build-roots"
+ [ -n "$sget_new_roots" ] || return 0
+ sget_all_roots=$sget_new_roots,$sget_all_roots
+ done
+}
+
sget_install()
{
sget_plan=0
@@ -323,18 +493,13 @@ sget_install()
done
else
sget_note "resolving $sget_roots"
- if ! awk -v action=plan -v roots="$sget_roots" \
- -v include_build=1 -v program=sget \
- -v installed="$(sget_installed_names)" \
- -f "$SPS_LIBDIR/version.awk" -f "$SPS_LIBDIR/deps.awk" \
- "$sps_index" >"$sget_work/resolution"; then
- sps_die "$SPS_EX_DEPENDENCY" "cannot resolve requested packages"
- fi
+ sget_plan_with_builddeps
fi
sget_count=0
while IFS="$sps_tab" read -r sget_package sget_recipe sget_recipe_hash \
- sget_version sget_release sget_arch || [ -n "$sget_package" ]
+ sget_version sget_release sget_arch sget_unused_bdeps || \
+ [ -n "$sget_package" ]
do
[ -n "$sget_package" ] || continue
case $sget_arch in
diff --git a/bin/src b/bin/src
index 7c3d4cf..0cac56a 100755
--- a/bin/src
+++ b/bin/src
@@ -40,6 +40,38 @@ src_progress()
printf '%s: %s\n' "$sps_prog" "$*" >&2
}
+# True when the package directory has files/, patches/, or hooks/ (including
+# a dangling symlink of those names). Those still need the full definition
+# hasher; recipe-only packages can share one sha256sum batch.
+src_recipe_needs_support()
+{
+ src_support_dir=$1
+ for src_support_name in files patches hooks; do
+ if [ -e "$src_support_dir/$src_support_name" ] ||
+ [ -L "$src_support_dir/$src_support_name" ]; then
+ return 0
+ fi
+ done
+ return 1
+}
+
+# Print hashing progress. Small repositories still log every recipe; large
+# ones log the first, last, and every 25th so stderr stays useful without
+# becoming the slow path.
+src_progress_hash()
+{
+ src_ph_repo=$1
+ src_ph_n=$2
+ src_ph_total=$3
+ src_ph_label=$4
+ if [ "$src_ph_total" -le 8 ] ||
+ [ "$src_ph_n" -eq 1 ] ||
+ [ "$src_ph_n" -eq "$src_ph_total" ] ||
+ [ $((src_ph_n % 25)) -eq 0 ]; then
+ src_progress "$src_ph_repo $src_ph_n/$src_ph_total $src_ph_label"
+ fi
+}
+
src_need_index()
{
[ -r "$sps_index" ] ||
@@ -55,6 +87,7 @@ src_cleanup_update()
if [ -n "${src_work:-}" ]; then
rm -f "$src_work/repos.raw" "$src_work/repos" \
"$src_work/recipes.unsorted" "$src_work/recipes" \
+ "$src_work/simple" "$src_work/simple.raw" \
"$src_work/digests" "$src_work/packages.index"
rmdir "$src_work" 2>/dev/null || :
src_work=
@@ -101,9 +134,11 @@ src_sync_git()
src_progress "cloning '$src_git_name'"
# Depth 1 is enough for recipes. Later fetches stay full so
# fast-forward and divergence checks keep working.
- # --progress keeps clone/fetch from looking hung when stderr
- # is not a terminal (setup, logs, scripted live installs).
- if ! git clone --depth 1 --single-branch --progress -- \
+ # --no-tags skips tag objects that would otherwise bloat fetch
+ # on remotes with a long tag history. Branch tips are still
+ # fetched. --progress keeps clone/fetch from looking hung when
+ # stderr is not a terminal (setup, logs, scripted live installs).
+ if ! git clone --depth 1 --single-branch --no-tags --progress -- \
"$src_git_source" "$src_clone_checkout"; then
sps_die "$SPS_EX_NETWORK" \
"cannot clone repository '$src_git_name'"
@@ -148,7 +183,7 @@ src_sync_git()
fi
src_progress "fetching '$src_git_name'"
- if ! git -C "$src_git_checkout" fetch --progress origin; then
+ if ! git -C "$src_git_checkout" fetch --no-tags --progress origin; then
sps_die "$SPS_EX_NETWORK" \
"cannot fetch repository '$src_git_name'"
fi
@@ -304,34 +339,92 @@ src_update()
src_failed=1
break
}
+ : > "$src_work/simple" || {
+ printf "%s: cannot create recipe hash list\n" "$sps_prog" >&2
+ src_failed=1
+ break
+ }
src_repo_packages=0
src_recipe_total=$(awk 'END { print NR + 0 }' "$src_work/recipes")
src_progress "indexing '$src_repo_name' ($src_recipe_total recipes)"
while IFS= read -r src_recipe || [ -n "$src_recipe" ]
do
[ -n "$src_recipe" ] || continue
- if ! sps_tab_safe "$src_recipe"; then
- printf "%s: recipe path contains a tab or newline: %s\n" \
+ if src_recipe_needs_support "${src_recipe%/*}"; then
+ continue
+ fi
+ printf '%s\n' "$src_recipe" >> "$src_work/simple" || {
+ printf "%s: cannot record recipe hash list: %s\n" \
"$sps_prog" "$src_recipe" >&2
src_failed=1
break
+ }
+ done < "$src_work/recipes"
+ [ "$src_failed" -eq 0 ] || break
+
+ src_batch_ok=0
+ if command -v sha256sum >/dev/null 2>&1 &&
+ command -v xargs >/dev/null 2>&1 &&
+ [ -s "$src_work/simple" ]; then
+ if tr '\n' '\0' < "$src_work/simple" |
+ xargs -0 sha256sum -- > "$src_work/simple.raw"; then
+ src_simple_n=$(awk 'END { print NR + 0 }' "$src_work/simple")
+ src_hash_n=$(awk 'END { print NR + 0 }' "$src_work/simple.raw")
+ if [ "$src_simple_n" -eq "$src_hash_n" ]; then
+ src_batch_ok=1
+ fi
fi
- src_digest=$(sps_definition_sha256 "$src_recipe") || {
- printf "%s: cannot hash package definition: %s\n" \
- "$sps_prog" "$src_recipe" >&2
+ fi
+ if [ "$src_batch_ok" -eq 1 ]; then
+ exec 3< "$src_work/simple.raw" || {
+ printf "%s: cannot read batched recipe hashes\n" \
+ "$sps_prog" >&2
src_failed=1
break
}
+ fi
+ while IFS= read -r src_recipe || [ -n "$src_recipe" ]
+ do
+ [ -n "$src_recipe" ] || continue
+ if ! sps_tab_safe "$src_recipe"; then
+ printf "%s: recipe path contains a tab or newline: %s\n" \
+ "$sps_prog" "$src_recipe" >&2
+ src_failed=1
+ break
+ fi
+ if [ "$src_batch_ok" -eq 1 ] &&
+ ! src_recipe_needs_support "${src_recipe%/*}"; then
+ read -r src_file_hash src_hash_rest <&3 || {
+ printf "%s: batched recipe hash missing: %s\n" \
+ "$sps_prog" "$src_recipe" >&2
+ src_failed=1
+ break
+ }
+ src_digest=$(sps_definition_from_recipe_file_hash \
+ "$src_file_hash") || {
+ printf "%s: cannot hash package definition: %s\n" \
+ "$sps_prog" "$src_recipe" >&2
+ src_failed=1
+ break
+ }
+ else
+ src_digest=$(sps_definition_sha256 "$src_recipe") || {
+ printf "%s: cannot hash package definition: %s\n" \
+ "$sps_prog" "$src_recipe" >&2
+ src_failed=1
+ break
+ }
+ fi
case $src_digest in
''|*[!0123456789abcdefABCDEF]*)
- printf "%s: invalid package definition SHA-256: %s\n" \
+ printf "%s: invalid package definition SHA-256: %s\n" \
"$sps_prog" "$src_recipe" >&2
src_failed=1
break
;;
esac
if [ "${#src_digest}" -ne 64 ]; then
- printf "%s: invalid package definition SHA-256: %s\n" \
+ printf "%s: invalid package definition SHA-256: %s\n" \
"$sps_prog" "$src_recipe" >&2
src_failed=1
break
@@ -346,8 +439,12 @@ src_update()
src_repo_packages=$((src_repo_packages + 1))
src_recipe_label=${src_recipe%/*}
src_recipe_label=${src_recipe_label##*/}
- src_progress "$src_repo_name $src_repo_packages/$src_recipe_total $src_recipe_label"
+ src_progress_hash "$src_repo_name" "$src_repo_packages" \
+ "$src_recipe_total" "$src_recipe_label"
done < "$src_work/recipes"
+ if [ "$src_batch_ok" -eq 1 ]; then
+ exec 3<&-
+ fi
[ "$src_failed" -eq 0 ] || break
if [ "$src_repo_packages" -gt 0 ]; then
@@ -469,7 +566,26 @@ src_query()
src_check()
{
src_check_deps=0
- src_check_recipes=
+ src_check_recipes_file=
+ src_check_work=
+ src_check_cleanup()
+ {
+ [ -n "${src_check_recipes_file:-}" ] &&
+ rm -f "$src_check_recipes_file"
+ src_check_recipes_file=
+ if [ -n "${src_check_work:-}" ]; then
+ rm -f "$src_check_work/packages.index"
+ rmdir "$src_check_work" 2>/dev/null || :
+ src_check_work=
+ fi
+ }
+ src_check_recipes_file=$(mktemp "${TMPDIR:-/tmp}/sps-src-check.XXXXXX") ||
+ exit "$SPS_EX_PERMISSION"
+ trap src_check_cleanup 0
+ trap 'exit 129' 1
+ trap 'exit 130' 2
+ trap 'exit 131' 3
+ trap 'exit 143' 15
while [ "$#" -gt 0 ]; do
case $1 in
--deps)
@@ -477,10 +593,14 @@ src_check()
;;
--help|-h)
printf '%s\n' 'usage: src check RECIPE... [--deps]'
+ src_check_cleanup
+ trap - 0 1 2 3 15
exit 0
;;
-*)
src_usage >&2
+ src_check_cleanup
+ trap - 0 1 2 3 15
exit "$SPS_EX_USAGE"
;;
*)
@@ -492,18 +612,23 @@ src_check()
if [ ! -f "$src_check_resolve" ] ||
[ -L "$src_check_resolve" ] ||
[ ! -r "$src_check_resolve" ]; then
+ src_check_cleanup
+ trap - 0 1 2 3 15
sps_die "$SPS_EX_NOTFOUND" \
"no readable recipe file at '$1'"
fi
- src_check_recipes="$src_check_recipes $src_check_resolve"
+ printf '%s\n' "$src_check_resolve" >>"$src_check_recipes_file" ||
+ sps_die "$SPS_EX_PERMISSION" "cannot record recipe path"
;;
esac
shift
done
- [ -n "$src_check_recipes" ] || {
+ if [ ! -s "$src_check_recipes_file" ]; then
printf '%s\n' 'usage: src check RECIPE... [--deps]' >&2
+ src_check_cleanup
+ trap - 0 1 2 3 15
exit "$SPS_EX_USAGE"
- }
+ fi
if [ "$src_check_deps" -eq 1 ]; then
src_check_work=$(sps_mktemp_dir "$SPS_BUILD/.check") || exit $?
@@ -514,7 +639,8 @@ src_check()
src_check_failed=0
src_check_count=0
src_check_arch=${SPS_ARCH:-$(uname -m 2>/dev/null || printf unknown)}
- for src_check_recipe in $src_check_recipes; do
+ while IFS= read -r src_check_recipe || [ -n "$src_check_recipe" ]
+ do
[ -n "$src_check_recipe" ] || continue
src_check_count=$((src_check_count + 1))
src_check_digest=$(sps_definition_sha256 "$src_check_recipe") || {
@@ -550,11 +676,11 @@ src_check()
"$src_check_name" >&2
src_check_failed=1
fi
- done
+ done < "$src_check_recipes_file"
if [ "$src_check_failed" -ne 0 ]; then
- rm -f "$src_check_index" 2>/dev/null || :
- rmdir "$src_check_work" 2>/dev/null || :
+ src_check_cleanup
+ trap - 0 1 2 3 15
sps_die "$SPS_EX_PACKAGE" \
"$src_check_count recipe(s) checked: at least one failed"
fi
@@ -562,20 +688,22 @@ src_check()
if [ "$src_check_deps" -eq 1 ]; then
if ! awk -v action=validate -v program=src \
-f "$SPS_LIBDIR/index.awk" "$src_check_index"; then
- rmdir "$src_check_work" 2>/dev/null || :
+ src_check_cleanup
+ trap - 0 1 2 3 15
sps_die "$SPS_EX_PACKAGE" "generated check index is malformed"
fi
if ! awk -v action=validate -v program=src \
-f "$SPS_LIBDIR/version.awk" -f "$SPS_LIBDIR/deps.awk" \
"$src_check_index"; then
- rmdir "$src_check_work" 2>/dev/null || :
+ src_check_cleanup
+ trap - 0 1 2 3 15
sps_die "$SPS_EX_DEPENDENCY" "checked dependency graph is invalid"
fi
- rm -f "$src_check_index" 2>/dev/null || :
- rmdir "$src_check_work" 2>/dev/null || :
fi
printf 'checked %d recipe(s)\n' "$src_check_count"
+ src_check_cleanup
+ trap - 0 1 2 3 15
}
while [ "$#" -gt 0 ]; do
diff --git a/lib/common.sh b/lib/common.sh
index fb15608..3d1336a 100644
--- a/lib/common.sh
+++ b/lib/common.sh
@@ -88,45 +88,107 @@ sps_require_cmd()
sps_die "$SPS_EX_PACKAGE" "required command not found: $1"
}
-# Print only the digest. Avoid an extra awk process here.
-sps_sha256()
+# Resolve the hasher once per process. src update hashes hundreds of recipes.
+sps_sha256_kind=
+
+sps_sha256_detect()
{
+ [ -n "$sps_sha256_kind" ] && return 0
if command -v sha256sum >/dev/null 2>&1; then
- sps_hash_line=$(sha256sum "$1") || return $?
- printf '%s\n' "${sps_hash_line%% *}"
+ sps_sha256_kind=sha256sum
elif command -v sha256 >/dev/null 2>&1; then
- sha256 -q "$1"
+ sps_sha256_kind=sha256
elif command -v shasum >/dev/null 2>&1; then
- sps_hash_line=$(shasum -a 256 "$1") || return $?
- printf '%s\n' "${sps_hash_line%% *}"
+ sps_sha256_kind=shasum
elif command -v openssl >/dev/null 2>&1; then
- sps_hash_line=$(openssl dgst -sha256 "$1") || return $?
- printf '%s\n' "${sps_hash_line##* }"
+ sps_sha256_kind=openssl
else
sps_die "$SPS_EX_CHECKSUM" \
"no SHA-256 utility found (tried sha256sum, sha256, shasum, openssl)"
fi
}
+# Print only the digest. Avoid an extra awk process here.
+sps_sha256()
+{
+ sps_sha256_detect || return $?
+ case $sps_sha256_kind in
+ sha256sum)
+ sps_hash_line=$(sha256sum "$1") || return $?
+ printf '%s\n' "${sps_hash_line%% *}"
+ ;;
+ sha256)
+ sha256 -q "$1"
+ ;;
+ shasum)
+ sps_hash_line=$(shasum -a 256 "$1") || return $?
+ printf '%s\n' "${sps_hash_line%% *}"
+ ;;
+ openssl)
+ sps_hash_line=$(openssl dgst -sha256 "$1") || return $?
+ printf '%s\n' "${sps_hash_line##* }"
+ ;;
+ *)
+ sps_die "$SPS_EX_CHECKSUM" \
+ "no SHA-256 utility found (tried sha256sum, sha256, shasum, openssl)"
+ ;;
+ esac
+}
+
# Same digest as sps_sha256, from stdin. Used for the recipe-only definition
# manifest so src update does not create a throwaway directory per package.
sps_sha256_stream()
{
- if command -v sha256sum >/dev/null 2>&1; then
- sps_hash_line=$(sha256sum) || return $?
- printf '%s\n' "${sps_hash_line%% *}"
- elif command -v sha256 >/dev/null 2>&1; then
- sha256 -q
- elif command -v shasum >/dev/null 2>&1; then
- sps_hash_line=$(shasum -a 256) || return $?
- printf '%s\n' "${sps_hash_line%% *}"
- elif command -v openssl >/dev/null 2>&1; then
- sps_hash_line=$(openssl dgst -sha256) || return $?
- printf '%s\n' "${sps_hash_line##* }"
- else
- sps_die "$SPS_EX_CHECKSUM" \
- "no SHA-256 utility found (tried sha256sum, sha256, shasum, openssl)"
- fi
+ sps_sha256_detect || return $?
+ case $sps_sha256_kind in
+ sha256sum)
+ sps_hash_line=$(sha256sum) || return $?
+ printf '%s\n' "${sps_hash_line%% *}"
+ ;;
+ sha256)
+ sha256 -q
+ ;;
+ shasum)
+ sps_hash_line=$(shasum -a 256) || return $?
+ printf '%s\n' "${sps_hash_line%% *}"
+ ;;
+ openssl)
+ sps_hash_line=$(openssl dgst -sha256) || return $?
+ printf '%s\n' "${sps_hash_line##* }"
+ ;;
+ *)
+ sps_die "$SPS_EX_CHECKSUM" \
+ "no SHA-256 utility found (tried sha256sum, sha256, shasum, openssl)"
+ ;;
+ esac
+}
+
+# Lowercase a SHA-256 hex digest. GNU sha256sum is already lowercase; skip tr.
+sps_hex_normalize()
+{
+ sps_hex_in=$1
+ case $sps_hex_in in
+ *[ABCDEF]*)
+ sps_hex_in=$(printf '%s\n' "$sps_hex_in" |
+ LC_ALL=C tr 'ABCDEF' 'abcdef') || return 1
+ ;;
+ esac
+ case $sps_hex_in in
+ ''|*[!0123456789abcdef]*) return 1 ;;
+ esac
+ [ "${#sps_hex_in}" -eq 64 ] || return 1
+ printf '%s\n' "$sps_hex_in"
+}
+
+# Recipe-only definition digest from the recipe file's SHA-256. Same two-line
+# manifest the support-tree path would write for a package with no files/,
+# patches/, or hooks/.
+sps_definition_from_recipe_file_hash()
+{
+ sps_rfh=$(sps_hex_normalize "$1") || return 1
+ sps_rfh_out=$(printf 'sps-package-definition\t1\nfile\t-\t%s\trecipe\n' \
+ "$sps_rfh" | sps_sha256_stream) || return 1
+ sps_hex_normalize "$sps_rfh_out"
}
# Hash every input which makes up one package definition. The manifest uses
@@ -178,23 +240,11 @@ sps_definition_sha256()
sps_warn 'cannot hash package definition recipe'
return 1
}
- sps_definition_hash=$(printf '%s\n' "$sps_definition_hash" |
- LC_ALL=C tr 'ABCDEF' 'abcdef') || return 1
- case $sps_definition_hash in
- ''|*[!0123456789abcdef]*) return 1 ;;
- esac
- [ "${#sps_definition_hash}" -eq 64 ] || return 1
- sps_definition_out=$(printf 'sps-package-definition\t1\nfile\t-\t%s\trecipe\n' \
- "$sps_definition_hash" | sps_sha256_stream) || {
+ sps_definition_out=$(sps_definition_from_recipe_file_hash \
+ "$sps_definition_hash") || {
sps_warn 'cannot hash package definition manifest'
return 1
}
- sps_definition_out=$(printf '%s\n' "$sps_definition_out" |
- LC_ALL=C tr 'ABCDEF' 'abcdef') || return 1
- case $sps_definition_out in
- ''|*[!0123456789abcdef]*) return 1 ;;
- esac
- [ "${#sps_definition_out}" -eq 64 ] || return 1
printf '%s\n' "$sps_definition_out"
return 0
fi
diff --git a/lib/deps.awk b/lib/deps.awk
index 097ccbf..aa6b643 100644
--- a/lib/deps.awk
+++ b/lib/deps.awk
@@ -270,7 +270,8 @@ END {
if (action == "plan")
print deps_package, deps_recipe[deps_package],
deps_recipe_sha256[deps_package], deps_version[deps_package],
- deps_release[deps_package], deps_arch[deps_package]
+ deps_release[deps_package], deps_arch[deps_package],
+ deps_build[deps_package]
else
print deps_package
}
diff --git a/lib/setup/extras b/lib/setup/extras
index 19a1062..d425f8f 100644
--- a/lib/setup/extras
+++ b/lib/setup/extras
@@ -1,20 +1,24 @@
# 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,
+# mkiso refuse anything else. Do not list unpackaged names (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
+brightnessctl brightnessctl
btop btop
+chrony chrony
cmake CMake
cryptsetup cryptsetup
cups CUPS printing
curl curl
dhcpcd dhcpcd
+dmidecode dmidecode
doas OpenDoas
+dos2unix dos2unix
dosfstools dosfstools
e2fsprogs e2fsprogs
emacs GNU Emacs
@@ -24,17 +28,20 @@ eza eza
falkon Falkon
fastfetch fastfetch
fd fd
+feh feh
firefox Firefox
fish fish shell
flatpak Flatpak
fzf fzf
gdb GDB
git Git
+git-lfs Git LFS
gnupg GnuPG
go Go
gptfdisk GPT fdisk
helix Helix editor
htop htop
+imagemagick ImageMagick
intel-ucode Intel CPU microcode
iperf3 iperf3
iwd iwd
@@ -49,6 +56,7 @@ mdadm Linux MD RAID
mesa Mesa
meson Meson
micro micro editor
+mpv mpv
nano GNU nano
neovim Neovim
networkmanager NetworkManager
@@ -60,7 +68,9 @@ nouveau Nouveau (NVIDIA open driver)
ntfs-3g NTFS driver
nvidia-open NVIDIA open kernel modules
openssh OpenSSH
+pandoc pandoc
parted GNU parted
+pass pass
perl Perl
pipewire PipeWire
python Python
@@ -73,10 +83,15 @@ smartmontools S.M.A.R.T. tools
starship starship prompt
strace strace
sudo sudo
+syncthing Syncthing
+tig tig
tmux tmux
vim Vim
vulkan-tools vulkaninfo
wget wget
wireguard-tools WireGuard tools
+xdg-utils xdg-utils
yq yq
+yt-dlp yt-dlp
+zellij zellij
zsh Z shell
diff --git a/lib/setup/sets/daily-drive.set b/lib/setup/sets/daily-drive.set
new file mode 100644
index 0000000..2f6c568
--- /dev/null
+++ b/lib/setup/sets/daily-drive.set
@@ -0,0 +1,5 @@
+name daily-drive
+title Daily driver extras
+description Media player, documents, sync, clipboard, and laptop tools on top of the QoL CLI set.
+default 0
+package daily-drive
diff --git a/lib/setup/sets/multimedia.set b/lib/setup/sets/multimedia.set
index 97f9016..21085c5 100644
--- a/lib/setup/sets/multimedia.set
+++ b/lib/setup/sets/multimedia.set
@@ -1,6 +1,6 @@
name multimedia
title Multimedia
-description PipeWire, WirePlumber, FFmpeg, and desktop audio/video libraries.
+description PipeWire, WirePlumber, FFmpeg, mpv, and desktop audio/video libraries.
default 0
package pipewire
package wireplumber
@@ -11,3 +11,9 @@ package gst-plugins-good
package alsa-lib
package alsa-utils
package vulkan-loader
+package mpv
+package yt-dlp
+package sox
+package cmus
+package libass
+package dav1d
diff --git a/lib/setup/sets/office.set b/lib/setup/sets/office.set
index aa813b4..3d6c9a2 100644
--- a/lib/setup/sets/office.set
+++ b/lib/setup/sets/office.set
@@ -1,8 +1,14 @@
name office
title Office and documents
-description Okular, Ark, and related document tools. LibreOffice is not packaged here.
+description Okular, Ark, document viewers, and image tools. LibreOffice is not packaged here.
default 0
package okular
package ark
package gwenview
package spectacle
+package imagemagick
+package ghostscript
+package mupdf
+package hunspell
+package pandoc
+package typst
diff --git a/man/mkpkg.1 b/man/mkpkg.1
index 80f9f36..a5b02ab 100644
--- a/man/mkpkg.1
+++ b/man/mkpkg.1
@@ -164,6 +164,14 @@ pkg-config files
.BR LIBRARY_PATH ,
.BR PKG_CONFIG_SYSROOT_DIR ,
.BR CMAKE_PREFIX_PATH ).
+.B OPENSSL_MODULES
+and
+.B OPENSSL_CONF
+point at providers and
+.B openssl.cnf
+already installed in that root so a just-built Python
+.B _ssl
+module does not load the live disc's OpenSSL.
Live
.BR setup (1)
installs into a mounted target with the live compiler; without this, a
diff --git a/man/sget.1 b/man/sget.1
index ddc3706..b0de196 100644
--- a/man/sget.1
+++ b/man/sget.1
@@ -71,6 +71,14 @@ build dependencies may participate.
Resolve and install requested packages. Requested roots are explicit; packages
needed only by the graph are dependency-installed. Exact installed roots are
promoted to explicit without an unnecessary reinstall.
+Build dependencies are installed only when a package will be compiled.
+If
+.BR cmake (1) ,
+.BR ninja (1) ,
+.BR meson (1) ,
+or another well-known build tool is already on
+.B PATH
+and is not a runtime requirement, that host command is used instead.
.TP
.B upgrade
Resolve the installed explicit world set and upgrade its selected dependency
diff --git a/man/src.1 b/man/src.1
index cdea836..eb946d3 100644
--- a/man/src.1
+++ b/man/src.1
@@ -21,14 +21,19 @@ checkouts together with configured local directory repositories.
.B update
Clone absent Git repositories with
.B --depth 1
-(later fetches stay full), fetch existing clean Git repositories,
-fast-forward safe tracking branches, scan configured trees, validate recipes
-and the selected dependency graph, and atomically replace the local package
-index. Invalid input leaves the previous index in place. Recipe-only packages
-are hashed without a temporary directory; a repository is parsed in one AWK
-process rather than one process per recipe.
-Progress is written to stderr for each repository, each recipe hash, Git
-clone/fetch, and index validation so a long update does not look idle.
+and
+.B --no-tags
+(later fetches stay full and also skip tags), fetch existing clean Git
+repositories, fast-forward safe tracking branches, scan configured trees,
+validate recipes and the selected dependency graph, and atomically replace
+the local package index. Invalid input leaves the previous index in place.
+Recipe-only packages are hashed in one
+.B sha256sum
+batch; a repository is parsed in one AWK process rather than one process
+per recipe. The definition digest is unchanged.
+Progress is written to stderr for each repository, Git clone/fetch, and
+index validation. Recipe hashing logs the first and last package plus every
+25th in large trees so a long update does not look idle.
.B -q
keeps only the final summary line on stdout.
.TP
diff --git a/tests/test_build.sh b/tests/test_build.sh
index 55b3ccb..a3c3cb8 100755
--- a/tests/test_build.sh
+++ b/tests/test_build.sh
@@ -206,7 +206,7 @@ esac
# 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" \
+ mkdir -p "$tmp/root/usr/include" "$tmp/root/usr/lib64/ossl-modules" \
"$tmp/link-recipe" "$tmp/link-out"
cat >"$tmp/root/usr/include/spsprobe.h" <<'EOF'
int sps_probe_lib(void);
@@ -290,11 +290,12 @@ arch any
description Confirms just-built binaries load libraries from SPS_ROOT
configure printf '%s\n' "$LD_LIBRARY_PATH" >"$WORK/ldpath"
configure printf '%s\n' "$SPS_TARGET_LIBRARY_PATH" >"$WORK/targetld"
+configure printf '%s\n' "${OPENSSL_MODULES-}" >"$WORK/osslmod"
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"
configure "$WORK/probe"
install mkdir -p "$PKG/usr/share/target-runtime-lib-probe" "$PKG/usr/bin"
-install cp "$WORK/ldpath" "$WORK/targetld" "$PKG/usr/share/target-runtime-lib-probe/"
+install cp "$WORK/ldpath" "$WORK/targetld" "$WORK/osslmod" "$PKG/usr/share/target-runtime-lib-probe/"
install cp "$WORK/probe" "$PKG/usr/bin/target-runtime-lib-probe"
install chmod 755 "$PKG/usr/bin/target-runtime-lib-probe"
EOF
@@ -310,6 +311,9 @@ EOF
assert_equal "$tmp/root/usr/lib64:$tmp/root/usr/lib:$tmp/root/lib64:$tmp/root/lib" \
"$(cat "$tmp/run-extract/usr/share/target-runtime-lib-probe/targetld")" \
"SPS_TARGET_LIBRARY_PATH was not the target libdirs"
+ assert_equal "$tmp/root/usr/lib64/ossl-modules" \
+ "$(cat "$tmp/run-extract/usr/share/target-runtime-lib-probe/osslmod")" \
+ "OPENSSL_MODULES was not the target OpenSSL provider directory"
[ -x "$tmp/run-extract/usr/bin/target-runtime-lib-probe" ] ||
fail "runtime probe binary was not staged"
fi
diff --git a/tests/test_dependencies.sh b/tests/test_dependencies.sh
index 07569b6..e7e735b 100755
--- a/tests/test_dependencies.sh
+++ b/tests/test_dependencies.sh
@@ -28,6 +28,7 @@ trap 'rm -rf "$tmp"' 0 1 2 3 15
mkdir -p "$tmp/root" "$tmp/cache" "$tmp/db/installed" "$tmp/build" \
"$tmp/repo/base" "$tmp/repo/lib" "$tmp/repo/tool" "$tmp/repo/app" \
"$tmp/repo/left" "$tmp/repo/right" "$tmp/repo/diamond" \
+ "$tmp/repo/openssl" "$tmp/repo/cmake" "$tmp/repo/gui" \
"$tmp/fakebin"
: > "$tmp/sps.conf"
printf 'repo test %s 10\n' "$tmp/repo" > "$tmp/repos.conf"
@@ -58,6 +59,9 @@ make_recipe "$tmp/repo/app" app 4.0 'depend lib>=2.0' 'builddep tool'
make_recipe "$tmp/repo/left" left 1.0 'depend base'
make_recipe "$tmp/repo/right" right 1.0 'depend base'
make_recipe "$tmp/repo/diamond" diamond 1.0 'depend right left'
+make_recipe "$tmp/repo/openssl" openssl 1.0
+make_recipe "$tmp/repo/cmake" cmake 1.0 'depend openssl'
+make_recipe "$tmp/repo/gui" gui 1.0 'depend base' 'builddep cmake'
mkdir -p "$tmp/repo/app/files" "$tmp/repo/app/patches" \
"$tmp/repo/app/hooks"
printf '%s\n' 'default=true' >"$tmp/repo/app/files/app.conf"
@@ -94,10 +98,25 @@ why_path=$("$sget" why diamond base)
plan=$("$sget" install --plan app)
plan_names=$(printf '%s\n' "$plan" |
awk '/^ / { print $2 }')
-expected_names=$(printf '%s\n' base lib tool app)
+expected_names=$(printf '%s\n' tool base lib app)
[ "$plan_names" = "$expected_names" ] ||
fail "unexpected dependency order: $plan_names"
+if command -v cmake >/dev/null 2>&1; then
+ gui_plan=$("$sget" install --plan gui)
+ case $gui_plan in
+ *cmake*) fail 'host cmake was still planned as a build-only tool' ;;
+ *openssl*) fail 'openssl was pulled only because cmake is a builddep' ;;
+ esac
+ contains "$gui_plan" gui
+ contains "$gui_plan" base
+ gui_err=$("$sget" install --plan gui 2>&1 >/dev/null) || true
+ case $gui_err in
+ *'using host cmake to build gui'*) ;;
+ *) fail "install plan did not report using host cmake: $gui_err" ;;
+ esac
+fi
+
nodeps_plan=$("$sget" install --plan --nodeps app)
nodeps_names=$(printf '%s\n' "$nodeps_plan" |
awk '/^ / { print $2 }')
diff --git a/tests/test_mkiso.sh b/tests/test_mkiso.sh
index 4047d3f..94851eb 100755
--- a/tests/test_mkiso.sh
+++ b/tests/test_mkiso.sh
@@ -357,6 +357,8 @@ grep -q SPS_HOST_LD_LIBRARY_PATH "$project_dir/bin/mkpkg" ||
fail 'mkpkg must keep host gcc from loading SPS_ROOT via LD_LIBRARY_PATH'
grep -q SPS_TARGET_LIBRARY_PATH "$project_dir/bin/mkpkg" ||
fail 'mkpkg must put SPS_ROOT on LD_LIBRARY_PATH for just-built test programs'
+grep -q OPENSSL_MODULES "$project_dir/bin/mkpkg" ||
+ fail 'mkpkg must point OpenSSL at SPS_ROOT providers during target builds'
grep -q CONFIG_SITE "$project_dir/bin/mkpkg" ||
fail 'mkpkg must export CONFIG_SITE so autoconf sees SPS_ROOT LDFLAGS'
grep -q 'sps_say "unpacking' "$project_dir/bin/pkin" ||
@@ -372,8 +374,8 @@ then
else
fail 'extras must not list htop, btop, or tmux more than once'
fi
-if grep -E '^(mpv|gimp|libreoffice|qemu|screen)[[:space:]]' \
+if grep -E '^(gimp|libreoffice|qemu)[[:space:]]' \
"$project_dir/lib/setup/extras"
then
- fail 'extras must not offer unpackaged names such as mpv'
+ fail 'extras must not offer unpackaged names such as GIMP or LibreOffice'
fi
diff --git a/tests/test_repository.sh b/tests/test_repository.sh
index 3ad01a6..c873a49 100755
--- a/tests/test_repository.sh
+++ b/tests/test_repository.sh
@@ -136,6 +136,20 @@ status_output=$("$src" status)
contains "$status_output" 'core: 2 packages'
contains "$status_output" 'extra: 1 packages'
+# Batched recipe-only hashing and the support-tree hasher must agree with
+# sps_definition_sha256 so a large extra tree does not change index bytes.
+. "$project_dir/lib/common.sh"
+sps_program=test_repository
+tie_digest=$(sps_definition_sha256 "$tmp/core/base/tie/recipe") ||
+ fail 'could not hash recipe-only tie'
+alpha_digest=$(sps_definition_sha256 "$tmp/core/base/alpha/recipe") ||
+ fail 'could not hash support-tree alpha'
+[ "$(awk -F '\t' '$1 == "tie" { print $12; exit }' "$index")" = "$tie_digest" ] ||
+ fail 'indexed tie digest does not match sps_definition_sha256'
+[ "$(awk -F '\t' '$1 == "alpha" && $8 == "core" { print $12; exit }' "$index")" = \
+ "$alpha_digest" ] ||
+ fail 'indexed alpha digest does not match sps_definition_sha256'
+
old_index=$(cksum "$index")
cp "$tmp/repos.conf" "$tmp/repos.good"
diff --git a/tests/test_src_check.sh b/tests/test_src_check.sh
index aee1ccc..16c7560 100644
--- a/tests/test_src_check.sh
+++ b/tests/test_src_check.sh
@@ -54,6 +54,12 @@ $src check "$tmp/ok/recipe" | grep -q '^check: ok ' ||
# A directory argument resolves to its recipe file.
$src check "$tmp/ok/recipe" >/dev/null 2>&1 || fail "file recipe rejected"
+# Paths with spaces must not be word-split.
+mkdir -p "$tmp/ok dir"
+cp "$tmp/ok/recipe" "$tmp/ok dir/recipe"
+$src check "$tmp/ok dir" | grep -q '^check: ok ' ||
+ fail "recipe path with a space was not accepted"
+
# Missing the required install command is a syntax failure.
cat >"$tmp/bad/recipe" <<'EOF'
name badpkg