Stop handing dnf the comments that explain the package lists
Every list in setup/packages/ is annotated -- which package exists for which settings page, why an exception was made -- and install-packages passed the whole file to dnf, comment lines included. dnf does not ignore an argument it cannot match. It reports "No match for argument: #" and exits 1, and with set -euo pipefail at the top of that script the first annotated list ends the stage. initial-packages carries four comments and is the first list read, so a fresh machine got the repositories, the group updates, and then nothing. Two things hid it. On a machine that already has everything, a re-run matches every real name and fails only on the comments, so the failure looks like noise rather than the stage dying. And every contract that reads these lists strips comments with sed before comparing -- the tests were reading a file the installer was not, which is why a repository with a dependency contract, an assets contract and a doctor still reported PASS across the board. The fix is one filter used at all five call sites. The contract lifts that filter out of the script and runs it, rather than describing what it should do, so deleting or renaming it fails here instead of passing quietly. It also checks the inverse -- that stripping comments does not strip packages -- because trading a loud failure for a silent one would be worse than the bug. Found while adding the extras lists for phase 4, which are annotated the same way and would have hit the same wall. Claude-Session: https://claude.ai/code/session_01NvgBuSWB5sE43yWmg21ozj
This commit is contained in:
Executable
+108
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# The comments in a package list are for the reader, not for dnf.
|
||||
#
|
||||
# Every list in setup/packages/ is annotated -- which package exists for which
|
||||
# settings page, why an exception was made -- and install-packages passed the
|
||||
# whole file to dnf, comment lines included. dnf does not ignore an argument it
|
||||
# cannot match; it reports "No match for argument: #" and exits 1. With
|
||||
# `set -euo pipefail` at the top of that script, the first annotated list ends
|
||||
# the stage, and a fresh machine gets almost no packages.
|
||||
#
|
||||
# It could not show up here. This machine has everything already, so a re-run
|
||||
# matches every real name and only fails on the comments; and every contract
|
||||
# that reads these lists strips comments with sed before comparing, so the tests
|
||||
# were reading a file the installer was not. That is the exact shape of bug this
|
||||
# repository's fresh-install work exists to find: invisible on the machine it
|
||||
# was written on, fatal on the next one.
|
||||
#
|
||||
# The check runs the installer's own extraction rather than describing it, so
|
||||
# renaming or deleting the filter fails here instead of silently passing.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
installer="$repo_dir/setup/scripts/install-packages"
|
||||
lists_dir="$repo_dir/setup/packages"
|
||||
|
||||
findings=()
|
||||
note() { findings+=("$1"); }
|
||||
|
||||
# ── The installer's own filter, lifted out and run ───────────────────────────
|
||||
|
||||
filter="$(sed -n '/^packages_in()/,/^}/p' "$installer")"
|
||||
if [[ -z "$filter" ]]; then
|
||||
printf 'package lists contract: install-packages defines no packages_in filter, so it reads lists raw\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
eval "$filter"
|
||||
|
||||
work="$(mktemp -d)"
|
||||
trap 'rm -rf "$work"' EXIT
|
||||
|
||||
fixture="$work/list"
|
||||
cat >"$fixture" <<'LIST'
|
||||
# A whole-line comment, which is what broke the install.
|
||||
alpha
|
||||
bravo
|
||||
|
||||
#Indented and unspaced forms of the same thing.
|
||||
# spaced
|
||||
charlie
|
||||
LIST
|
||||
|
||||
read -ra extracted <<<"$(packages_in "$fixture")"
|
||||
|
||||
expected=(alpha bravo charlie)
|
||||
if [[ "${extracted[*]}" != "${expected[*]}" ]]; then
|
||||
note "a commented list extracts as '${extracted[*]}' rather than '${expected[*]}'"
|
||||
fi
|
||||
|
||||
for token in "${extracted[@]}"; do
|
||||
[[ "$token" == *"#"* ]] && note "the extracted list still carries a comment marker: $token"
|
||||
done
|
||||
|
||||
# ── Every real list survives it ──────────────────────────────────────────────
|
||||
#
|
||||
# Stripping comments must not also strip packages. Each name that is not a
|
||||
# comment has to come out the other side, or this fix trades a loud failure for
|
||||
# a quiet one.
|
||||
|
||||
shopt -s nullglob
|
||||
for list in "$lists_dir"/*; do
|
||||
[[ -f "$list" ]] || continue
|
||||
name="$(basename "$list")"
|
||||
|
||||
# What a reader would say the file declares.
|
||||
mapfile -t declared < <(sed 's/#.*//' "$list" | tr -d ' \t' | grep -v '^$' | sort)
|
||||
read -ra passed <<<"$(packages_in "$list")"
|
||||
mapfile -t passed_sorted < <(printf '%s\n' "${passed[@]}" | sort)
|
||||
|
||||
if [[ "${declared[*]}" != "${passed_sorted[*]}" ]]; then
|
||||
note "$name declares ${#declared[@]} packages but the installer would pass ${#passed_sorted[@]}"
|
||||
fi
|
||||
|
||||
for token in "${passed[@]}"; do
|
||||
[[ "$token" == *"#"* ]] && note "$name would pass a comment marker to the package manager"
|
||||
done
|
||||
done
|
||||
|
||||
# ── No list is read raw any more ─────────────────────────────────────────────
|
||||
#
|
||||
# The filter existing is not the same as it being used. A single missed call
|
||||
# site is a stage that still dies on the list it forgot.
|
||||
|
||||
if grep -nE 'tr "\\n" " " *<' "$installer" >/dev/null; then
|
||||
note 'a package list is still read with tr rather than through packages_in'
|
||||
fi
|
||||
|
||||
# ── Report ───────────────────────────────────────────────────────────────────
|
||||
|
||||
if (( ${#findings[@]} > 0 )); then
|
||||
mapfile -t findings < <(printf '%s\n' "${findings[@]}" | sort -u)
|
||||
printf 'package lists contract: %d finding(s)\n' "${#findings[@]}" >&2
|
||||
printf ' - %s\n' "${findings[@]}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf 'package lists contract: PASS\n'
|
||||
Reference in New Issue
Block a user