1*1f7f31bfSJohn Moon#!/bin/bash 2*1f7f31bfSJohn Moon# SPDX-License-Identifier: GPL-2.0-only 3*1f7f31bfSJohn Moon# Script to check commits for UAPI backwards compatibility 4*1f7f31bfSJohn Moon 5*1f7f31bfSJohn Moonset -o errexit 6*1f7f31bfSJohn Moonset -o pipefail 7*1f7f31bfSJohn Moon 8*1f7f31bfSJohn Moonprint_usage() { 9*1f7f31bfSJohn Moon name=$(basename "$0") 10*1f7f31bfSJohn Moon cat << EOF 11*1f7f31bfSJohn Moon$name - check for UAPI header stability across Git commits 12*1f7f31bfSJohn Moon 13*1f7f31bfSJohn MoonBy default, the script will check to make sure the latest commit (or current 14*1f7f31bfSJohn Moondirty changes) did not introduce ABI changes when compared to HEAD^1. You can 15*1f7f31bfSJohn Mooncheck against additional commit ranges with the -b and -p options. 16*1f7f31bfSJohn Moon 17*1f7f31bfSJohn MoonThe script will not check UAPI headers for architectures other than the one 18*1f7f31bfSJohn Moondefined in ARCH. 19*1f7f31bfSJohn Moon 20*1f7f31bfSJohn MoonUsage: $name [-b BASE_REF] [-p PAST_REF] [-j N] [-l ERROR_LOG] [-i] [-q] [-v] 21*1f7f31bfSJohn Moon 22*1f7f31bfSJohn MoonOptions: 23*1f7f31bfSJohn Moon -b BASE_REF Base git reference to use for comparison. If unspecified or empty, 24*1f7f31bfSJohn Moon will use any dirty changes in tree to UAPI files. If there are no 25*1f7f31bfSJohn Moon dirty changes, HEAD will be used. 26*1f7f31bfSJohn Moon -p PAST_REF Compare BASE_REF to PAST_REF (e.g. -p v6.1). If unspecified or empty, 27*1f7f31bfSJohn Moon will use BASE_REF^1. Must be an ancestor of BASE_REF. Only headers 28*1f7f31bfSJohn Moon that exist on PAST_REF will be checked for compatibility. 29*1f7f31bfSJohn Moon -j JOBS Number of checks to run in parallel (default: number of CPU cores). 30*1f7f31bfSJohn Moon -l ERROR_LOG Write error log to file (default: no error log is generated). 31*1f7f31bfSJohn Moon -i Ignore ambiguous changes that may or may not break UAPI compatibility. 32*1f7f31bfSJohn Moon -q Quiet operation. 33*1f7f31bfSJohn Moon -v Verbose operation (print more information about each header being checked). 34*1f7f31bfSJohn Moon 35*1f7f31bfSJohn MoonEnvironmental args: 36*1f7f31bfSJohn Moon ABIDIFF Custom path to abidiff binary 37*1f7f31bfSJohn Moon CC C compiler (default is "gcc") 38*1f7f31bfSJohn Moon ARCH Target architecture for the UAPI check (default is host arch) 39*1f7f31bfSJohn Moon 40*1f7f31bfSJohn MoonExit codes: 41*1f7f31bfSJohn Moon $SUCCESS) Success 42*1f7f31bfSJohn Moon $FAIL_ABI) ABI difference detected 43*1f7f31bfSJohn Moon $FAIL_PREREQ) Prerequisite not met 44*1f7f31bfSJohn MoonEOF 45*1f7f31bfSJohn Moon} 46*1f7f31bfSJohn Moon 47*1f7f31bfSJohn Moonreadonly SUCCESS=0 48*1f7f31bfSJohn Moonreadonly FAIL_ABI=1 49*1f7f31bfSJohn Moonreadonly FAIL_PREREQ=2 50*1f7f31bfSJohn Moon 51*1f7f31bfSJohn Moon# Print to stderr 52*1f7f31bfSJohn Mooneprintf() { 53*1f7f31bfSJohn Moon # shellcheck disable=SC2059 54*1f7f31bfSJohn Moon printf "$@" >&2 55*1f7f31bfSJohn Moon} 56*1f7f31bfSJohn Moon 57*1f7f31bfSJohn Moon# Expand an array with a specific character (similar to Python string.join()) 58*1f7f31bfSJohn Moonjoin() { 59*1f7f31bfSJohn Moon local IFS="$1" 60*1f7f31bfSJohn Moon shift 61*1f7f31bfSJohn Moon printf "%s" "$*" 62*1f7f31bfSJohn Moon} 63*1f7f31bfSJohn Moon 64*1f7f31bfSJohn Moon# Create abidiff suppressions 65*1f7f31bfSJohn Moongen_suppressions() { 66*1f7f31bfSJohn Moon # Common enum variant names which we don't want to worry about 67*1f7f31bfSJohn Moon # being shifted when new variants are added. 68*1f7f31bfSJohn Moon local -a enum_regex=( 69*1f7f31bfSJohn Moon ".*_AFTER_LAST$" 70*1f7f31bfSJohn Moon ".*_CNT$" 71*1f7f31bfSJohn Moon ".*_COUNT$" 72*1f7f31bfSJohn Moon ".*_END$" 73*1f7f31bfSJohn Moon ".*_LAST$" 74*1f7f31bfSJohn Moon ".*_MASK$" 75*1f7f31bfSJohn Moon ".*_MAX$" 76*1f7f31bfSJohn Moon ".*_MAX_BIT$" 77*1f7f31bfSJohn Moon ".*_MAX_BPF_ATTACH_TYPE$" 78*1f7f31bfSJohn Moon ".*_MAX_ID$" 79*1f7f31bfSJohn Moon ".*_MAX_SHIFT$" 80*1f7f31bfSJohn Moon ".*_NBITS$" 81*1f7f31bfSJohn Moon ".*_NETDEV_NUMHOOKS$" 82*1f7f31bfSJohn Moon ".*_NFT_META_IIFTYPE$" 83*1f7f31bfSJohn Moon ".*_NL80211_ATTR$" 84*1f7f31bfSJohn Moon ".*_NLDEV_NUM_OPS$" 85*1f7f31bfSJohn Moon ".*_NUM$" 86*1f7f31bfSJohn Moon ".*_NUM_ELEMS$" 87*1f7f31bfSJohn Moon ".*_NUM_IRQS$" 88*1f7f31bfSJohn Moon ".*_SIZE$" 89*1f7f31bfSJohn Moon ".*_TLSMAX$" 90*1f7f31bfSJohn Moon "^MAX_.*" 91*1f7f31bfSJohn Moon "^NUM_.*" 92*1f7f31bfSJohn Moon ) 93*1f7f31bfSJohn Moon 94*1f7f31bfSJohn Moon # Common padding field names which can be expanded into 95*1f7f31bfSJohn Moon # without worrying about users. 96*1f7f31bfSJohn Moon local -a padding_regex=( 97*1f7f31bfSJohn Moon ".*end$" 98*1f7f31bfSJohn Moon ".*pad$" 99*1f7f31bfSJohn Moon ".*pad[0-9]?$" 100*1f7f31bfSJohn Moon ".*pad_[0-9]?$" 101*1f7f31bfSJohn Moon ".*padding$" 102*1f7f31bfSJohn Moon ".*padding[0-9]?$" 103*1f7f31bfSJohn Moon ".*padding_[0-9]?$" 104*1f7f31bfSJohn Moon ".*res$" 105*1f7f31bfSJohn Moon ".*resv$" 106*1f7f31bfSJohn Moon ".*resv[0-9]?$" 107*1f7f31bfSJohn Moon ".*resv_[0-9]?$" 108*1f7f31bfSJohn Moon ".*reserved$" 109*1f7f31bfSJohn Moon ".*reserved[0-9]?$" 110*1f7f31bfSJohn Moon ".*reserved_[0-9]?$" 111*1f7f31bfSJohn Moon ".*rsvd[0-9]?$" 112*1f7f31bfSJohn Moon ".*unused$" 113*1f7f31bfSJohn Moon ) 114*1f7f31bfSJohn Moon 115*1f7f31bfSJohn Moon cat << EOF 116*1f7f31bfSJohn Moon[suppress_type] 117*1f7f31bfSJohn Moon type_kind = enum 118*1f7f31bfSJohn Moon changed_enumerators_regexp = $(join , "${enum_regex[@]}") 119*1f7f31bfSJohn MoonEOF 120*1f7f31bfSJohn Moon 121*1f7f31bfSJohn Moon for p in "${padding_regex[@]}"; do 122*1f7f31bfSJohn Moon cat << EOF 123*1f7f31bfSJohn Moon[suppress_type] 124*1f7f31bfSJohn Moon type_kind = struct 125*1f7f31bfSJohn Moon has_data_member_inserted_at = offset_of_first_data_member_regexp(${p}) 126*1f7f31bfSJohn MoonEOF 127*1f7f31bfSJohn Moon done 128*1f7f31bfSJohn Moon 129*1f7f31bfSJohn Moonif [ "$IGNORE_AMBIGUOUS_CHANGES" = "true" ]; then 130*1f7f31bfSJohn Moon cat << EOF 131*1f7f31bfSJohn Moon[suppress_type] 132*1f7f31bfSJohn Moon type_kind = struct 133*1f7f31bfSJohn Moon has_data_member_inserted_at = end 134*1f7f31bfSJohn Moon has_size_change = yes 135*1f7f31bfSJohn MoonEOF 136*1f7f31bfSJohn Moonfi 137*1f7f31bfSJohn Moon} 138*1f7f31bfSJohn Moon 139*1f7f31bfSJohn Moon# Check if git tree is dirty 140*1f7f31bfSJohn Moontree_is_dirty() { 141*1f7f31bfSJohn Moon ! git diff --quiet 142*1f7f31bfSJohn Moon} 143*1f7f31bfSJohn Moon 144*1f7f31bfSJohn Moon# Get list of files installed in $ref 145*1f7f31bfSJohn Moonget_file_list() { 146*1f7f31bfSJohn Moon local -r ref="$1" 147*1f7f31bfSJohn Moon local -r tree="$(get_header_tree "$ref")" 148*1f7f31bfSJohn Moon 149*1f7f31bfSJohn Moon # Print all installed headers, filtering out ones that can't be compiled 150*1f7f31bfSJohn Moon find "$tree" -type f -name '*.h' -printf '%P\n' | grep -v -f "$INCOMPAT_LIST" 151*1f7f31bfSJohn Moon} 152*1f7f31bfSJohn Moon 153*1f7f31bfSJohn Moon# Add to the list of incompatible headers 154*1f7f31bfSJohn Moonadd_to_incompat_list() { 155*1f7f31bfSJohn Moon local -r ref="$1" 156*1f7f31bfSJohn Moon 157*1f7f31bfSJohn Moon # Start with the usr/include/Makefile to get a list of the headers 158*1f7f31bfSJohn Moon # that don't compile using this method. 159*1f7f31bfSJohn Moon if [ ! -f usr/include/Makefile ]; then 160*1f7f31bfSJohn Moon eprintf "error - no usr/include/Makefile present at %s\n" "$ref" 161*1f7f31bfSJohn Moon eprintf "Note: usr/include/Makefile was added in the v5.3 kernel release\n" 162*1f7f31bfSJohn Moon exit "$FAIL_PREREQ" 163*1f7f31bfSJohn Moon fi 164*1f7f31bfSJohn Moon { 165*1f7f31bfSJohn Moon # shellcheck disable=SC2016 166*1f7f31bfSJohn Moon printf 'all: ; @echo $(no-header-test)\n' 167*1f7f31bfSJohn Moon cat usr/include/Makefile 168*1f7f31bfSJohn Moon } | SRCARCH="$ARCH" make --always-make -f - | tr " " "\n" \ 169*1f7f31bfSJohn Moon | grep -v "asm-generic" >> "$INCOMPAT_LIST" 170*1f7f31bfSJohn Moon 171*1f7f31bfSJohn Moon # The makefile also skips all asm-generic files, but prints "asm-generic/%" 172*1f7f31bfSJohn Moon # which won't work for our grep match. Instead, print something grep will match. 173*1f7f31bfSJohn Moon printf "asm-generic/.*\.h\n" >> "$INCOMPAT_LIST" 174*1f7f31bfSJohn Moon} 175*1f7f31bfSJohn Moon 176*1f7f31bfSJohn Moon# Compile the simple test app 177*1f7f31bfSJohn Moondo_compile() { 178*1f7f31bfSJohn Moon local -r inc_dir="$1" 179*1f7f31bfSJohn Moon local -r header="$2" 180*1f7f31bfSJohn Moon local -r out="$3" 181*1f7f31bfSJohn Moon printf "int main(void) { return 0; }\n" | \ 182*1f7f31bfSJohn Moon "$CC" -c \ 183*1f7f31bfSJohn Moon -o "$out" \ 184*1f7f31bfSJohn Moon -x c \ 185*1f7f31bfSJohn Moon -O0 \ 186*1f7f31bfSJohn Moon -std=c90 \ 187*1f7f31bfSJohn Moon -fno-eliminate-unused-debug-types \ 188*1f7f31bfSJohn Moon -g \ 189*1f7f31bfSJohn Moon "-I${inc_dir}" \ 190*1f7f31bfSJohn Moon -include "$header" \ 191*1f7f31bfSJohn Moon - 192*1f7f31bfSJohn Moon} 193*1f7f31bfSJohn Moon 194*1f7f31bfSJohn Moon# Run make headers_install 195*1f7f31bfSJohn Moonrun_make_headers_install() { 196*1f7f31bfSJohn Moon local -r ref="$1" 197*1f7f31bfSJohn Moon local -r install_dir="$(get_header_tree "$ref")" 198*1f7f31bfSJohn Moon make -j "$MAX_THREADS" ARCH="$ARCH" INSTALL_HDR_PATH="$install_dir" \ 199*1f7f31bfSJohn Moon headers_install > /dev/null 200*1f7f31bfSJohn Moon} 201*1f7f31bfSJohn Moon 202*1f7f31bfSJohn Moon# Install headers for both git refs 203*1f7f31bfSJohn Mooninstall_headers() { 204*1f7f31bfSJohn Moon local -r base_ref="$1" 205*1f7f31bfSJohn Moon local -r past_ref="$2" 206*1f7f31bfSJohn Moon 207*1f7f31bfSJohn Moon for ref in "$base_ref" "$past_ref"; do 208*1f7f31bfSJohn Moon printf "Installing user-facing UAPI headers from %s... " "${ref:-dirty tree}" 209*1f7f31bfSJohn Moon if [ -n "$ref" ]; then 210*1f7f31bfSJohn Moon git archive --format=tar --prefix="${ref}-archive/" "$ref" \ 211*1f7f31bfSJohn Moon | (cd "$TMP_DIR" && tar xf -) 212*1f7f31bfSJohn Moon ( 213*1f7f31bfSJohn Moon cd "${TMP_DIR}/${ref}-archive" 214*1f7f31bfSJohn Moon run_make_headers_install "$ref" 215*1f7f31bfSJohn Moon add_to_incompat_list "$ref" "$INCOMPAT_LIST" 216*1f7f31bfSJohn Moon ) 217*1f7f31bfSJohn Moon else 218*1f7f31bfSJohn Moon run_make_headers_install "$ref" 219*1f7f31bfSJohn Moon add_to_incompat_list "$ref" "$INCOMPAT_LIST" 220*1f7f31bfSJohn Moon fi 221*1f7f31bfSJohn Moon printf "OK\n" 222*1f7f31bfSJohn Moon done 223*1f7f31bfSJohn Moon sort -u -o "$INCOMPAT_LIST" "$INCOMPAT_LIST" 224*1f7f31bfSJohn Moon sed -i -e '/^$/d' "$INCOMPAT_LIST" 225*1f7f31bfSJohn Moon} 226*1f7f31bfSJohn Moon 227*1f7f31bfSJohn Moon# Print the path to the headers_install tree for a given ref 228*1f7f31bfSJohn Moonget_header_tree() { 229*1f7f31bfSJohn Moon local -r ref="$1" 230*1f7f31bfSJohn Moon printf "%s" "${TMP_DIR}/${ref}/usr" 231*1f7f31bfSJohn Moon} 232*1f7f31bfSJohn Moon 233*1f7f31bfSJohn Moon# Check file list for UAPI compatibility 234*1f7f31bfSJohn Mooncheck_uapi_files() { 235*1f7f31bfSJohn Moon local -r base_ref="$1" 236*1f7f31bfSJohn Moon local -r past_ref="$2" 237*1f7f31bfSJohn Moon local -r abi_error_log="$3" 238*1f7f31bfSJohn Moon 239*1f7f31bfSJohn Moon local passed=0; 240*1f7f31bfSJohn Moon local failed=0; 241*1f7f31bfSJohn Moon local -a threads=() 242*1f7f31bfSJohn Moon set -o errexit 243*1f7f31bfSJohn Moon 244*1f7f31bfSJohn Moon printf "Checking changes to UAPI headers between %s and %s...\n" "$past_ref" "${base_ref:-dirty tree}" 245*1f7f31bfSJohn Moon # Loop over all UAPI headers that were installed by $past_ref (if they only exist on $base_ref, 246*1f7f31bfSJohn Moon # there's no way they're broken and no way to compare anyway) 247*1f7f31bfSJohn Moon while read -r file; do 248*1f7f31bfSJohn Moon if [ "${#threads[@]}" -ge "$MAX_THREADS" ]; then 249*1f7f31bfSJohn Moon if wait "${threads[0]}"; then 250*1f7f31bfSJohn Moon passed=$((passed + 1)) 251*1f7f31bfSJohn Moon else 252*1f7f31bfSJohn Moon failed=$((failed + 1)) 253*1f7f31bfSJohn Moon fi 254*1f7f31bfSJohn Moon threads=("${threads[@]:1}") 255*1f7f31bfSJohn Moon fi 256*1f7f31bfSJohn Moon 257*1f7f31bfSJohn Moon check_individual_file "$base_ref" "$past_ref" "$file" & 258*1f7f31bfSJohn Moon threads+=("$!") 259*1f7f31bfSJohn Moon done < <(get_file_list "$past_ref") 260*1f7f31bfSJohn Moon 261*1f7f31bfSJohn Moon for t in "${threads[@]}"; do 262*1f7f31bfSJohn Moon if wait "$t"; then 263*1f7f31bfSJohn Moon passed=$((passed + 1)) 264*1f7f31bfSJohn Moon else 265*1f7f31bfSJohn Moon failed=$((failed + 1)) 266*1f7f31bfSJohn Moon fi 267*1f7f31bfSJohn Moon done 268*1f7f31bfSJohn Moon 269*1f7f31bfSJohn Moon if [ -n "$abi_error_log" ]; then 270*1f7f31bfSJohn Moon printf 'Generated by "%s %s" from git ref %s\n\n' \ 271*1f7f31bfSJohn Moon "$0" "$*" "$(git rev-parse HEAD)" > "$abi_error_log" 272*1f7f31bfSJohn Moon fi 273*1f7f31bfSJohn Moon 274*1f7f31bfSJohn Moon while read -r error_file; do 275*1f7f31bfSJohn Moon { 276*1f7f31bfSJohn Moon cat "$error_file" 277*1f7f31bfSJohn Moon printf "\n\n" 278*1f7f31bfSJohn Moon } | tee -a "${abi_error_log:-/dev/null}" >&2 279*1f7f31bfSJohn Moon done < <(find "$TMP_DIR" -type f -name '*.error' | sort) 280*1f7f31bfSJohn Moon 281*1f7f31bfSJohn Moon total="$((passed + failed))" 282*1f7f31bfSJohn Moon if [ "$failed" -gt 0 ]; then 283*1f7f31bfSJohn Moon eprintf "error - %d/%d UAPI headers compatible with %s appear _not_ to be backwards compatible\n" \ 284*1f7f31bfSJohn Moon "$failed" "$total" "$ARCH" 285*1f7f31bfSJohn Moon if [ -n "$abi_error_log" ]; then 286*1f7f31bfSJohn Moon eprintf "Failure summary saved to %s\n" "$abi_error_log" 287*1f7f31bfSJohn Moon fi 288*1f7f31bfSJohn Moon else 289*1f7f31bfSJohn Moon printf "All %d UAPI headers compatible with %s appear to be backwards compatible\n" \ 290*1f7f31bfSJohn Moon "$total" "$ARCH" 291*1f7f31bfSJohn Moon fi 292*1f7f31bfSJohn Moon 293*1f7f31bfSJohn Moon return "$failed" 294*1f7f31bfSJohn Moon} 295*1f7f31bfSJohn Moon 296*1f7f31bfSJohn Moon# Check an individual file for UAPI compatibility 297*1f7f31bfSJohn Mooncheck_individual_file() { 298*1f7f31bfSJohn Moon local -r base_ref="$1" 299*1f7f31bfSJohn Moon local -r past_ref="$2" 300*1f7f31bfSJohn Moon local -r file="$3" 301*1f7f31bfSJohn Moon 302*1f7f31bfSJohn Moon local -r base_header="$(get_header_tree "$base_ref")/${file}" 303*1f7f31bfSJohn Moon local -r past_header="$(get_header_tree "$past_ref")/${file}" 304*1f7f31bfSJohn Moon 305*1f7f31bfSJohn Moon if [ ! -f "$base_header" ]; then 306*1f7f31bfSJohn Moon mkdir -p "$(dirname "$base_header")" 307*1f7f31bfSJohn Moon printf "==== UAPI header %s was removed between %s and %s ====" \ 308*1f7f31bfSJohn Moon "$file" "$past_ref" "$base_ref" \ 309*1f7f31bfSJohn Moon > "${base_header}.error" 310*1f7f31bfSJohn Moon return 1 311*1f7f31bfSJohn Moon fi 312*1f7f31bfSJohn Moon 313*1f7f31bfSJohn Moon compare_abi "$file" "$base_header" "$past_header" "$base_ref" "$past_ref" 314*1f7f31bfSJohn Moon} 315*1f7f31bfSJohn Moon 316*1f7f31bfSJohn Moon# Perform the A/B compilation and compare output ABI 317*1f7f31bfSJohn Mooncompare_abi() { 318*1f7f31bfSJohn Moon local -r file="$1" 319*1f7f31bfSJohn Moon local -r base_header="$2" 320*1f7f31bfSJohn Moon local -r past_header="$3" 321*1f7f31bfSJohn Moon local -r base_ref="$4" 322*1f7f31bfSJohn Moon local -r past_ref="$5" 323*1f7f31bfSJohn Moon local -r log="${TMP_DIR}/log/${file}.log" 324*1f7f31bfSJohn Moon local -r error_log="${TMP_DIR}/log/${file}.error" 325*1f7f31bfSJohn Moon 326*1f7f31bfSJohn Moon mkdir -p "$(dirname "$log")" 327*1f7f31bfSJohn Moon 328*1f7f31bfSJohn Moon if ! do_compile "$(get_header_tree "$base_ref")/include" "$base_header" "${base_header}.bin" 2> "$log"; then 329*1f7f31bfSJohn Moon { 330*1f7f31bfSJohn Moon warn_str=$(printf "==== Could not compile version of UAPI header %s at %s ====\n" \ 331*1f7f31bfSJohn Moon "$file" "$base_ref") 332*1f7f31bfSJohn Moon printf "%s\n" "$warn_str" 333*1f7f31bfSJohn Moon cat "$log" 334*1f7f31bfSJohn Moon printf -- "=%.0s" $(seq 0 ${#warn_str}) 335*1f7f31bfSJohn Moon } > "$error_log" 336*1f7f31bfSJohn Moon return 1 337*1f7f31bfSJohn Moon fi 338*1f7f31bfSJohn Moon 339*1f7f31bfSJohn Moon if ! do_compile "$(get_header_tree "$past_ref")/include" "$past_header" "${past_header}.bin" 2> "$log"; then 340*1f7f31bfSJohn Moon { 341*1f7f31bfSJohn Moon warn_str=$(printf "==== Could not compile version of UAPI header %s at %s ====\n" \ 342*1f7f31bfSJohn Moon "$file" "$past_ref") 343*1f7f31bfSJohn Moon printf "%s\n" "$warn_str" 344*1f7f31bfSJohn Moon cat "$log" 345*1f7f31bfSJohn Moon printf -- "=%.0s" $(seq 0 ${#warn_str}) 346*1f7f31bfSJohn Moon } > "$error_log" 347*1f7f31bfSJohn Moon return 1 348*1f7f31bfSJohn Moon fi 349*1f7f31bfSJohn Moon 350*1f7f31bfSJohn Moon local ret=0 351*1f7f31bfSJohn Moon "$ABIDIFF" --non-reachable-types \ 352*1f7f31bfSJohn Moon --suppressions "$SUPPRESSIONS" \ 353*1f7f31bfSJohn Moon "${past_header}.bin" "${base_header}.bin" > "$log" || ret="$?" 354*1f7f31bfSJohn Moon if [ "$ret" -eq 0 ]; then 355*1f7f31bfSJohn Moon if [ "$VERBOSE" = "true" ]; then 356*1f7f31bfSJohn Moon printf "No ABI differences detected in %s from %s -> %s\n" \ 357*1f7f31bfSJohn Moon "$file" "$past_ref" "$base_ref" 358*1f7f31bfSJohn Moon fi 359*1f7f31bfSJohn Moon else 360*1f7f31bfSJohn Moon # Bits in abidiff's return code can be used to determine the type of error 361*1f7f31bfSJohn Moon if [ $((ret & 0x2)) -gt 0 ]; then 362*1f7f31bfSJohn Moon eprintf "error - abidiff did not run properly\n" 363*1f7f31bfSJohn Moon exit 1 364*1f7f31bfSJohn Moon fi 365*1f7f31bfSJohn Moon 366*1f7f31bfSJohn Moon if [ "$IGNORE_AMBIGUOUS_CHANGES" = "true" ] && [ "$ret" -eq 4 ]; then 367*1f7f31bfSJohn Moon return 0 368*1f7f31bfSJohn Moon fi 369*1f7f31bfSJohn Moon 370*1f7f31bfSJohn Moon # If the only changes were additions (not modifications to existing APIs), then 371*1f7f31bfSJohn Moon # there's no problem. Ignore these diffs. 372*1f7f31bfSJohn Moon if grep "Unreachable types summary" "$log" | grep -q "0 removed" && 373*1f7f31bfSJohn Moon grep "Unreachable types summary" "$log" | grep -q "0 changed"; then 374*1f7f31bfSJohn Moon return 0 375*1f7f31bfSJohn Moon fi 376*1f7f31bfSJohn Moon 377*1f7f31bfSJohn Moon { 378*1f7f31bfSJohn Moon warn_str=$(printf "==== ABI differences detected in %s from %s -> %s ====" \ 379*1f7f31bfSJohn Moon "$file" "$past_ref" "$base_ref") 380*1f7f31bfSJohn Moon printf "%s\n" "$warn_str" 381*1f7f31bfSJohn Moon sed -e '/summary:/d' -e '/changed type/d' -e '/^$/d' -e 's/^/ /g' "$log" 382*1f7f31bfSJohn Moon printf -- "=%.0s" $(seq 0 ${#warn_str}) 383*1f7f31bfSJohn Moon if cmp "$past_header" "$base_header" > /dev/null 2>&1; then 384*1f7f31bfSJohn Moon printf "\n%s did not change between %s and %s...\n" "$file" "$past_ref" "${base_ref:-dirty tree}" 385*1f7f31bfSJohn Moon printf "It's possible a change to one of the headers it includes caused this error:\n" 386*1f7f31bfSJohn Moon grep '^#include' "$base_header" 387*1f7f31bfSJohn Moon printf "\n" 388*1f7f31bfSJohn Moon fi 389*1f7f31bfSJohn Moon } > "$error_log" 390*1f7f31bfSJohn Moon 391*1f7f31bfSJohn Moon return 1 392*1f7f31bfSJohn Moon fi 393*1f7f31bfSJohn Moon} 394*1f7f31bfSJohn Moon 395*1f7f31bfSJohn Moon# Check that a minimum software version number is satisfied 396*1f7f31bfSJohn Moonmin_version_is_satisfied() { 397*1f7f31bfSJohn Moon local -r min_version="$1" 398*1f7f31bfSJohn Moon local -r version_installed="$2" 399*1f7f31bfSJohn Moon 400*1f7f31bfSJohn Moon printf "%s\n%s\n" "$min_version" "$version_installed" \ 401*1f7f31bfSJohn Moon | sort -Vc > /dev/null 2>&1 402*1f7f31bfSJohn Moon} 403*1f7f31bfSJohn Moon 404*1f7f31bfSJohn Moon# Make sure we have the tools we need and the arguments make sense 405*1f7f31bfSJohn Mooncheck_deps() { 406*1f7f31bfSJohn Moon ABIDIFF="${ABIDIFF:-abidiff}" 407*1f7f31bfSJohn Moon CC="${CC:-gcc}" 408*1f7f31bfSJohn Moon ARCH="${ARCH:-$(uname -m)}" 409*1f7f31bfSJohn Moon if [ "$ARCH" = "x86_64" ]; then 410*1f7f31bfSJohn Moon ARCH="x86" 411*1f7f31bfSJohn Moon fi 412*1f7f31bfSJohn Moon 413*1f7f31bfSJohn Moon local -r abidiff_min_version="2.4" 414*1f7f31bfSJohn Moon local -r libdw_min_version_if_clang="0.171" 415*1f7f31bfSJohn Moon 416*1f7f31bfSJohn Moon if ! command -v "$ABIDIFF" > /dev/null 2>&1; then 417*1f7f31bfSJohn Moon eprintf "error - abidiff not found!\n" 418*1f7f31bfSJohn Moon eprintf "Please install abigail-tools version %s or greater\n" "$abidiff_min_version" 419*1f7f31bfSJohn Moon eprintf "See: https://sourceware.org/libabigail/manual/libabigail-overview.html\n" 420*1f7f31bfSJohn Moon return 1 421*1f7f31bfSJohn Moon fi 422*1f7f31bfSJohn Moon 423*1f7f31bfSJohn Moon local -r abidiff_version="$("$ABIDIFF" --version | cut -d ' ' -f 2)" 424*1f7f31bfSJohn Moon if ! min_version_is_satisfied "$abidiff_min_version" "$abidiff_version"; then 425*1f7f31bfSJohn Moon eprintf "error - abidiff version too old: %s\n" "$abidiff_version" 426*1f7f31bfSJohn Moon eprintf "Please install abigail-tools version %s or greater\n" "$abidiff_min_version" 427*1f7f31bfSJohn Moon eprintf "See: https://sourceware.org/libabigail/manual/libabigail-overview.html\n" 428*1f7f31bfSJohn Moon return 1 429*1f7f31bfSJohn Moon fi 430*1f7f31bfSJohn Moon 431*1f7f31bfSJohn Moon if ! command -v "$CC" > /dev/null 2>&1; then 432*1f7f31bfSJohn Moon eprintf 'error - %s not found\n' "$CC" 433*1f7f31bfSJohn Moon return 1 434*1f7f31bfSJohn Moon fi 435*1f7f31bfSJohn Moon 436*1f7f31bfSJohn Moon if "$CC" --version | grep -q clang; then 437*1f7f31bfSJohn Moon local -r libdw_version="$(ldconfig -v 2>/dev/null | grep -v SKIPPED | grep -m 1 -o 'libdw-[0-9]\+.[0-9]\+' | cut -c 7-)" 438*1f7f31bfSJohn Moon if ! min_version_is_satisfied "$libdw_min_version_if_clang" "$libdw_version"; then 439*1f7f31bfSJohn Moon eprintf "error - libdw version too old for use with clang: %s\n" "$libdw_version" 440*1f7f31bfSJohn Moon eprintf "Please install libdw from elfutils version %s or greater\n" "$libdw_min_version_if_clang" 441*1f7f31bfSJohn Moon eprintf "See: https://sourceware.org/elfutils/\n" 442*1f7f31bfSJohn Moon return 1 443*1f7f31bfSJohn Moon fi 444*1f7f31bfSJohn Moon fi 445*1f7f31bfSJohn Moon 446*1f7f31bfSJohn Moon if [ ! -d "arch/${ARCH}" ]; then 447*1f7f31bfSJohn Moon eprintf 'error - ARCH "%s" is not a subdirectory under arch/\n' "$ARCH" 448*1f7f31bfSJohn Moon eprintf "Please set ARCH to one of:\n%s\n" "$(find arch -maxdepth 1 -mindepth 1 -type d -printf '%f ' | fmt)" 449*1f7f31bfSJohn Moon return 1 450*1f7f31bfSJohn Moon fi 451*1f7f31bfSJohn Moon 452*1f7f31bfSJohn Moon if ! git rev-parse --is-inside-work-tree > /dev/null 2>&1; then 453*1f7f31bfSJohn Moon eprintf "error - this script requires the kernel tree to be initialized with Git\n" 454*1f7f31bfSJohn Moon return 1 455*1f7f31bfSJohn Moon fi 456*1f7f31bfSJohn Moon 457*1f7f31bfSJohn Moon if ! git rev-parse --verify "$past_ref" > /dev/null 2>&1; then 458*1f7f31bfSJohn Moon printf 'error - invalid git reference "%s"\n' "$past_ref" 459*1f7f31bfSJohn Moon return 1 460*1f7f31bfSJohn Moon fi 461*1f7f31bfSJohn Moon 462*1f7f31bfSJohn Moon if [ -n "$base_ref" ]; then 463*1f7f31bfSJohn Moon if ! git merge-base --is-ancestor "$past_ref" "$base_ref" > /dev/null 2>&1; then 464*1f7f31bfSJohn Moon printf 'error - "%s" is not an ancestor of base ref "%s"\n' "$past_ref" "$base_ref" 465*1f7f31bfSJohn Moon return 1 466*1f7f31bfSJohn Moon fi 467*1f7f31bfSJohn Moon if [ "$(git rev-parse "$base_ref")" = "$(git rev-parse "$past_ref")" ]; then 468*1f7f31bfSJohn Moon printf 'error - "%s" and "%s" are the same reference\n' "$past_ref" "$base_ref" 469*1f7f31bfSJohn Moon return 1 470*1f7f31bfSJohn Moon fi 471*1f7f31bfSJohn Moon fi 472*1f7f31bfSJohn Moon} 473*1f7f31bfSJohn Moon 474*1f7f31bfSJohn Moonrun() { 475*1f7f31bfSJohn Moon local base_ref="$1" 476*1f7f31bfSJohn Moon local past_ref="$2" 477*1f7f31bfSJohn Moon local abi_error_log="$3" 478*1f7f31bfSJohn Moon shift 3 479*1f7f31bfSJohn Moon 480*1f7f31bfSJohn Moon if [ -z "$KERNEL_SRC" ]; then 481*1f7f31bfSJohn Moon KERNEL_SRC="$(realpath "$(dirname "$0")"/..)" 482*1f7f31bfSJohn Moon fi 483*1f7f31bfSJohn Moon 484*1f7f31bfSJohn Moon cd "$KERNEL_SRC" 485*1f7f31bfSJohn Moon 486*1f7f31bfSJohn Moon if [ -z "$base_ref" ] && ! tree_is_dirty; then 487*1f7f31bfSJohn Moon base_ref=HEAD 488*1f7f31bfSJohn Moon fi 489*1f7f31bfSJohn Moon 490*1f7f31bfSJohn Moon if [ -z "$past_ref" ]; then 491*1f7f31bfSJohn Moon if [ -n "$base_ref" ]; then 492*1f7f31bfSJohn Moon past_ref="${base_ref}^1" 493*1f7f31bfSJohn Moon else 494*1f7f31bfSJohn Moon past_ref=HEAD 495*1f7f31bfSJohn Moon fi 496*1f7f31bfSJohn Moon fi 497*1f7f31bfSJohn Moon 498*1f7f31bfSJohn Moon if ! check_deps; then 499*1f7f31bfSJohn Moon exit "$FAIL_PREREQ" 500*1f7f31bfSJohn Moon fi 501*1f7f31bfSJohn Moon 502*1f7f31bfSJohn Moon TMP_DIR=$(mktemp -d) 503*1f7f31bfSJohn Moon readonly TMP_DIR 504*1f7f31bfSJohn Moon trap 'rm -rf "$TMP_DIR"' EXIT 505*1f7f31bfSJohn Moon 506*1f7f31bfSJohn Moon readonly INCOMPAT_LIST="${TMP_DIR}/incompat_list.txt" 507*1f7f31bfSJohn Moon touch "$INCOMPAT_LIST" 508*1f7f31bfSJohn Moon 509*1f7f31bfSJohn Moon readonly SUPPRESSIONS="${TMP_DIR}/suppressions.txt" 510*1f7f31bfSJohn Moon gen_suppressions > "$SUPPRESSIONS" 511*1f7f31bfSJohn Moon 512*1f7f31bfSJohn Moon # Run make install_headers for both refs 513*1f7f31bfSJohn Moon install_headers "$base_ref" "$past_ref" 514*1f7f31bfSJohn Moon 515*1f7f31bfSJohn Moon # Check for any differences in the installed header trees 516*1f7f31bfSJohn Moon if diff -r -q "$(get_header_tree "$base_ref")" "$(get_header_tree "$past_ref")" > /dev/null 2>&1; then 517*1f7f31bfSJohn Moon printf "No changes to UAPI headers were applied between %s and %s\n" "$past_ref" "${base_ref:-dirty tree}" 518*1f7f31bfSJohn Moon exit "$SUCCESS" 519*1f7f31bfSJohn Moon fi 520*1f7f31bfSJohn Moon 521*1f7f31bfSJohn Moon if ! check_uapi_files "$base_ref" "$past_ref" "$abi_error_log"; then 522*1f7f31bfSJohn Moon exit "$FAIL_ABI" 523*1f7f31bfSJohn Moon fi 524*1f7f31bfSJohn Moon} 525*1f7f31bfSJohn Moon 526*1f7f31bfSJohn Moonmain() { 527*1f7f31bfSJohn Moon MAX_THREADS=$(nproc) 528*1f7f31bfSJohn Moon VERBOSE="false" 529*1f7f31bfSJohn Moon IGNORE_AMBIGUOUS_CHANGES="false" 530*1f7f31bfSJohn Moon quiet="false" 531*1f7f31bfSJohn Moon local base_ref="" 532*1f7f31bfSJohn Moon while getopts "hb:p:j:l:iqv" opt; do 533*1f7f31bfSJohn Moon case $opt in 534*1f7f31bfSJohn Moon h) 535*1f7f31bfSJohn Moon print_usage 536*1f7f31bfSJohn Moon exit "$SUCCESS" 537*1f7f31bfSJohn Moon ;; 538*1f7f31bfSJohn Moon b) 539*1f7f31bfSJohn Moon base_ref="$OPTARG" 540*1f7f31bfSJohn Moon ;; 541*1f7f31bfSJohn Moon p) 542*1f7f31bfSJohn Moon past_ref="$OPTARG" 543*1f7f31bfSJohn Moon ;; 544*1f7f31bfSJohn Moon j) 545*1f7f31bfSJohn Moon MAX_THREADS="$OPTARG" 546*1f7f31bfSJohn Moon ;; 547*1f7f31bfSJohn Moon l) 548*1f7f31bfSJohn Moon abi_error_log="$OPTARG" 549*1f7f31bfSJohn Moon ;; 550*1f7f31bfSJohn Moon i) 551*1f7f31bfSJohn Moon IGNORE_AMBIGUOUS_CHANGES="true" 552*1f7f31bfSJohn Moon ;; 553*1f7f31bfSJohn Moon q) 554*1f7f31bfSJohn Moon quiet="true" 555*1f7f31bfSJohn Moon VERBOSE="false" 556*1f7f31bfSJohn Moon ;; 557*1f7f31bfSJohn Moon v) 558*1f7f31bfSJohn Moon VERBOSE="true" 559*1f7f31bfSJohn Moon quiet="false" 560*1f7f31bfSJohn Moon ;; 561*1f7f31bfSJohn Moon *) 562*1f7f31bfSJohn Moon exit "$FAIL_PREREQ" 563*1f7f31bfSJohn Moon esac 564*1f7f31bfSJohn Moon done 565*1f7f31bfSJohn Moon 566*1f7f31bfSJohn Moon if [ "$quiet" = "true" ]; then 567*1f7f31bfSJohn Moon exec > /dev/null 2>&1 568*1f7f31bfSJohn Moon fi 569*1f7f31bfSJohn Moon 570*1f7f31bfSJohn Moon run "$base_ref" "$past_ref" "$abi_error_log" "$@" 571*1f7f31bfSJohn Moon} 572*1f7f31bfSJohn Moon 573*1f7f31bfSJohn Moonmain "$@" 574