#!/bin/bash
#
# Detect broken packages that need to be rebuilt

shopt -s nullglob
shopt -s extglob
[ -t 0 ] || mapfile -t hook_targets

log="$(mktemp -d)"
trap 'rm -rf "$log"' EXIT

verbose=0
while getopts "vi:" OPTION; do
    case $OPTION in
        v) verbose=1 ;;
        i) include+=("$OPTARG") ;;
        *) exit 1 ;;
    esac
done

print_packages() {
    if (( ${#hook_targets[@]} )); then
        printf "%s\n" "${hook_targets[@]}" | xargs -rL1 pactree -rud1
    else
        pacman -Qq
    fi
}

filter_packages_by_repos() {
    mapfile -t repos_to_skip < <(for repo in $(pacconf --repo-list); do pacconf --repo="$repo" Server | grep -q "file://" || [[ " ${include[@]} " =~ " $repo " ]] || echo "$repo"; done)
    sort -u |
    comm -23 - <(pacman -Sql "${repos_to_skip[@]}" 2>/dev/null | sort)
}

get_package_files() {
    xargs -r pacman -Qql |
    perl -pe 's/\n/\0/' |
    xargs -r0 readlink -ez |
    sort -uz
}

filter_executable() {
    LANG=C xargs -r0 stat --printf "%F %a\t%n\0" |
    grep -ozP "^regular file \d?[1357]\d\d\t\K.*" |
    xargs -r0 file -N |
    grep -oP ".*(?=: ELF )"
}

check_broken_ldd() {
    ldd "$1" 2>/dev/null |
    grep "not found" > >( out="$(cat)"; f="$RANDOM"; (( verbose )) && [ -n "$out" ] && { echo -e "ldd $1\n" >> "$log/$f"; c++filt "$out" >> "$log/$f"; } ) &&
    pacman -Qqo "$1"
}

get_broken_ldd_pkgs() {
    export -f check_broken_ldd
    export log
    export verbose
    print_packages |
    filter_packages_by_repos |
    get_package_files |
    filter_executable |
    parallel --will-cite 'check_broken_ldd "{}"'
}

get_broken_python_pkgs() {
    command -v python >/dev/null || return
    python_version="$(python3 -c 'import sys; print (sys.version_info.minor)')"
    pkgs="$log/$RANDOM"
    pacman -Qqo /usr/lib/python3.!("$python_version") 2>/dev/null | grep -v '^python3[0-9]' | filter_packages_by_repos | tee "$pkgs"
    (( verbose )) && pacman -Qo /usr/lib/python3.!("$python_version") 2>/dev/null | grep -f "$pkgs" >"$log/$RANDOM"
    rm -f "$pkgs"
}

get_broken_perl_pkgs() {
    command -v perl >/dev/null || return
    perl_version="$(perl -E 'say $^V =~ /(\d+[.]\d+)/')"
    pkgs="$log/$RANDOM"
    pacman -Qqo /usr/lib/perl*/!("$perl_version") 2>/dev/null | filter_packages_by_repos | tee "$pkgs"
    (( verbose )) && pacman -Qo /usr/lib/perl*/!("$perl_version") 2>/dev/null | grep -f "$pkgs" >"$log/$RANDOM"
    rm -f "$pkgs"
}

get_broken_ruby_pkgs() {
    command -v ruby >&- && command -v gem >&- || return
    ruby_gemdir="$( gem environment gemdir )"
    ruby_version="${ruby_gemdir##*/}"
    ruby_install_path="${ruby_gemdir%/*}"
    pkgs="$log/$RANDOM"
    pacman -Qqo "$ruby_install_path"/!("$ruby_version") 2>/dev/null | filter_packages_by_repos | tee "$pkgs"
    (( verbose )) && pacman -Qo "$ruby_install_path"/!("$ruby_version") 2>/dev/null | grep -f "$pkgs" >"$log/$RANDOM"
    rm -f "$pkgs"
}

get_broken_haskell_pkgs() {
    command -v ghc >/dev/null || return
    haskell_version="$(ghc --numeric-version)"
    pkgs="$log/$RANDOM"
    pacman -Qqo /usr/lib/ghc-!("$haskell_version") 2>/dev/null | filter_packages_by_repos | tee "$pkgs"
    (( verbose )) && pacman -Qo /usr/lib/ghc-!("$haskell_version") 2>/dev/null | grep -f "$pkgs" >"$log/$RANDOM"
    rm -f "$pkgs"
}

get_broken_pkgs() {
    {
        get_broken_ldd_pkgs
        get_broken_python_pkgs
        get_broken_perl_pkgs
        get_broken_ruby_pkgs
        get_broken_haskell_pkgs
    } | sort -u
}

get_repo_pkgs() {
    pacman -Sl | cut -d' ' -f1-2
    pacman -Qqm | awk '{print "foreign", $0}'
}

join -12 <(get_repo_pkgs | sort -k2) <(get_broken_pkgs) | awk '{ print $2 "\t" $1 }'

if (( verbose )); then
    cd "$log"
    find . -not -empty -type f | while read -r f; do
        echo
        cat "$f"
    done
fi

exit 0
