aboutsummaryrefslogtreecommitdiff
path: root/scripts/audit-packages.sh
blob: f7af19ff1237741e48ab649370c1cb64380857ee (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#!/bin/bash
# audit-packages.sh — verify every package archsetup installs still exists
# at its declared source, and flag packages that moved between the official
# repos and the AUR.
#
# Extraction covers direct `pacman_install pkg` / `aur_install pkg` calls
# and `for software in a b c; do` loop lists (backslash continuations
# included). Variable arguments ("$software") are the loop plumbing, not
# packages, and are skipped.
#
# Checks:
#   official package missing from sync dbs  -> MOVED TO AUR (if AUR has it)
#                                              or MISSING EVERYWHERE
#   AUR package gone from the AUR           -> NOW IN OFFICIAL (if repos
#                                              have it) or MISSING EVERYWHERE
#
# Usage: audit-packages.sh [--list] [installer-script]
#   --list   print the extracted package lists and exit (no network)
#   default  audit; exit 1 when anything is missing or moved
#
# Env overrides (tests): AUDIT_PACMAN, AUDIT_CURL.

set -u

PACMAN="${AUDIT_PACMAN:-pacman}"
CURL="${AUDIT_CURL:-curl}"

mode=audit
case "${1:-}" in
    --list) mode=list; shift ;;
esac

script="${1:-$(dirname "$0")/../archsetup}"
if [ ! -r "$script" ]; then
    echo "audit-packages: cannot read installer script: $script" >&2
    exit 2
fi

# Extract package names per source. Loop lists are attributed to the
# install command used inside the loop body.
extract() {
    awk '
        # Collect a `for software in ...` list (may span backslash-continued
        # lines); attribute it when the loop body shows which installer runs.
        /for[ \t]+[A-Za-z_]+[ \t]+in[ \t]/ {
            line = $0
            sub(/^.*[ \t]in[ \t]+/, "", line)
            listpkgs = ""
            while (1) {
                stop = (line ~ /;[ \t]*do([ \t]|$)/)
                sub(/;[ \t]*do.*$/, "", line)
                gsub(/\\/, "", line)
                listpkgs = listpkgs " " line
                if (stop) break
                if ((getline line) <= 0) break
            }
            pending = listpkgs
            next
        }
        /^[ \t]*(pacman_install|aur_install)[ \t]/ {
            cmd = ($0 ~ /pacman_install/) ? "official" : "aur"
            arg = $2
            gsub(/["'\'']/, "", arg)
            if (arg ~ /^\$/) {
                # loop plumbing — emit the pending for-list under this source
                n = split(pending, w, /[ \t]+/)
                for (i = 1; i <= n; i++) if (w[i] != "") print cmd, w[i]
                pending = ""
            } else if (arg != "") {
                print cmd, arg
            }
        }
    ' "$script" | sort -u
}

pairs=$(extract)
official=$(echo "$pairs" | awk '$1 == "official" { print $2 }')
aur=$(echo "$pairs" | awk '$1 == "aur" { print $2 }')

if [ "$mode" = list ]; then
    echo "OFFICIAL ($(echo "$official" | grep -c .)):"
    echo "$official" | sed 's/^/  /'
    echo "AUR ($(echo "$aur" | grep -c .)):"
    echo "$aur" | sed 's/^/  /'
    exit 0
fi

# One batched AUR RPC query answers existence for every AUR-relevant name.
aur_query() {
    # args: package names; prints the names the AUR knows
    local args="" p
    for p in "$@"; do args="$args&arg[]=$p"; done
    "$CURL" -sm 20 "https://aur.archlinux.org/rpc/v5/info?${args#&}" 2>/dev/null \
        | tr ',' '\n' | sed -n 's/.*"Name":[[:space:]]*"\([^"]*\)".*/\1/p'
}

# shellcheck disable=SC2086
aur_known=$(aur_query $official $aur)

in_repos()  { "$PACMAN" -Si "$1" >/dev/null 2>&1; }
in_aur()    { echo "$aur_known" | grep -qx "$1"; }

moved_to_aur="" now_official="" missing=""
for p in $official; do
    in_repos "$p" && continue
    if in_aur "$p"; then moved_to_aur="$moved_to_aur $p"; else missing="$missing $p"; fi
done
for p in $aur; do
    if in_aur "$p"; then
        in_repos "$p" && now_official="$now_official $p (also in repos)"
        continue
    fi
    if in_repos "$p"; then now_official="$now_official $p"; else missing="$missing $p"; fi
done

rc=0
if [ -n "$missing" ]; then
    echo "MISSING EVERYWHERE (not in repos, not in AUR):"
    for p in $missing; do echo "  $p"; done
    rc=1
fi
if [ -n "$moved_to_aur" ]; then
    echo "MOVED TO AUR (pacman_install will fail; switch to aur_install):"
    for p in $moved_to_aur; do echo "  $p"; done
    rc=1
fi
if [ -n "$now_official" ]; then
    echo "NOW IN OFFICIAL (aur_install works but pacman_install is cleaner):"
    echo "$now_official" | tr ' ' '\n' | grep -v '^$' | sed 's/^/  /'
    rc=1
fi
total=$(( $(echo "$official" | grep -c .) + $(echo "$aur" | grep -c .) ))
echo "---"
echo "$total packages audited"
exit $rc