#!/bin/sh # checkapk - find ABI breakages in package upgrades # Copyright (c) 2012 Natanael Copa # # Distributed under GPL-2.0-only # program_version=@VERSION@ sharedir=${ABUILD_SHAREDIR:-@sharedir@} if ! [ -f "$sharedir/functions.sh" ]; then echo "$sharedir/functions.sh: not found" >&2 exit 1 fi . "$sharedir/functions.sh" usage() { cat <<-__EOF__ $program $program_version - find ABI breakages in package upgrades Usage: $program [-h|--help] Run in the directory of a built package. Options: -h, --help Print this help __EOF__ } args=$(getopt -o h --long help \ -n "$program" -- "$@") if [ $? -ne 0 ]; then usage >&2 exit 2 fi eval set -- "$args" while true; do case $1 in -h|--help) usage; exit 0;; --) shift; break;; *) exit 1;; # getopt error esac shift done if [ $# -gt 0 ]; then usage >&2 exit 2 fi if ! [ -f "$ABUILD_CONF" ] && ! [ -f "$ABUILD_USERCONF" ] && ! [ -f "$ABUILD_DEFCONF" ]; then die "no abuild.conf found" fi if ! [ -f APKBUILD ]; then die 'must be run in the directory of a built package' fi if ! [ -n "$CARCH" ]; then die "failed to detect CARCH" fi . ./APKBUILD startdir="$PWD" tmpdir=$(mktemp -d -t checkpkg-script.XXXXXX) trap "rm -rf '$tmpdir'; exit" INT EXIT cd "$tmpdir" || die "failed to create temp dir" # storage for downloaded/copied apks mkdir -p apks # default to pigz for unpacking gunzip="$(command -v pigz || echo gzip) -d" for i in $pkgname $subpackages; do _pkgname=${i%%:*} pkg=${_pkgname}-$pkgver-r$pkgrel pkgfile=${pkg}.apk repodir=${startdir%/*} repo=${repodir##*/} for filepath in "$PKGDEST"/$pkgfile "$REPODEST"/$repo/$CARCH/$pkgfile "$startdir"/$pkgfile; do if [ -f "$filepath" ]; then break fi done [ -f "$filepath" ] || die "can't find $pkgfile" # generate a temp repositories file with only the http(s) repos grep -E "^https?:" /etc/apk/repositories > "$tmpdir"/repositories oldpkg=$(apk fetch --repositories-file "$tmpdir"/repositories --simulate $_pkgname 2>&1 | sed 's/^Downloading //') if [ "${oldpkg}" = "${pkg}" ]; then die "the built package ($_pkgname) is already in the repo" fi # apk info could return multiple lines if multiple packages share a provide # (e.g. dnsmasq); filter with the exact package name and take the second line: # 7zip-23.01-r0 installed size: # 1668 KiB newsize="$(apk info --repositories-file /dev/null --repository "$REPODEST"/$repo --size $_pkgname | \ grep -F "$pkg" -A1 | \ tail -n1)" oldsize="$(apk info --repositories-file "$tmpdir"/repositories --size "$_pkgname" | \ grep -F "$oldpkg" -A1 | \ tail -n1)" if [ "$oldsize" = "$newsize" ]; then msg "No size differences for $_pkgname." else msg "Size difference for $_pkgname: $oldsize -> $newsize" fi apk fetch --quiet --repositories-file "$tmpdir"/repositories --stdout "$_pkgname" > apks/old.apk \ || msg2 "Old apk for $_pkgname missing. (new package/arch? broken internet?)" # pre-uncompress to not decompress twice # we do a decompression + tar -t for the file list, but then later we might do a full extraction for sodiff. # to not decompress here and then later again, store the intermediate tar $gunzip -c 2>/dev/null < apks/old.apk > apks/old.tar & $gunzip -c "$filepath" < "$filepath" > apks/new.tar & wait tar -t -f apks/old.tar 2>/dev/null | grep -v '^\.SIGN\.' | sort > "filelist-$_pkgname-old" & tar -t -f apks/new.tar | grep -v '^\.SIGN\.' | sort > "filelist-$_pkgname-new" & wait diff -U3 "filelist-$_pkgname-old" "filelist-$_pkgname-new" if diff -U0 "filelist-$_pkgname-old" "filelist-$_pkgname-new" | grep -q '\.so'; then echo "SODIFF:" mkdir -p "$_pkgname-pkg-old" "$_pkgname-pkg-new" tar -C "$_pkgname-pkg-old" 2>/dev/null -x -f apks/old.tar > /dev/null & tar -C "$_pkgname-pkg-new" -x -f apks/new.tar > /dev/null & wait # filter to things that start with -+ but strip the header (---/+++) diff -U0 "filelist-$_pkgname-old" "filelist-$_pkgname-new" | grep -E '^(\+|-)[A-Za-z0-9]+' | grep '\.so' | while read -r diff_sofile; do case "$diff_sofile" in -*) path="$_pkgname-pkg-old"; sofile="${diff_sofile#\-}" ;; +*) path="$_pkgname-pkg-new"; sofile="${diff_sofile#\+}" ;; esac # skip symlinks (only adds duplicate output or is dangling), and things that aren't valid elfs # matching .so above matches anything with .so in the name, e.g. xyz.sourceforge if ! [ -L "$path"/"$sofile" ] && readelf -h "$path"/"$sofile" >/dev/null 2>&1; then echo "$diff_sofile: " "$(objdump -p "$path"/"$sofile" | grep SONAME)" fi done else msg "No soname differences for $_pkgname." fi done