1#!/usr/bin/env bash 2# SPDX-License-Identifier: BSD-3-Clause 3# Copyright (C) 2019 Intel Corporation 4# All rights reserved. 5# 6shopt -s extglob 7 8if [ "$(uname -s)" = "FreeBSD" ]; then 9 echo "Not testing for shared object dependencies on FreeBSD." 10 exit 1 11fi 12 13testdir=$(readlink -f $(dirname $0)) 14rootdir=$(readlink -f $testdir/../..) 15 16function usage() { 17 script_name="$(basename $0)" 18 echo "Usage: $script_name" 19 echo " -c, --config-file Rebuilds SPDK according to config file from autotest" 20 echo " -a, --spdk-abi-path Use spdk-abi from specified path, otherwise" 21 echo " latest version is pulled and deleted after test" 22 echo " --release=RELEASE Compare ABI against RELEASE" 23 echo " -h, --help Print this help" 24 echo "Example:" 25 echo "$script_name -c ./autotest.config -a /path/to/spdk-abi" 26} 27 28# Parse input arguments # 29while getopts 'hc:a:-:' optchar; do 30 case "$optchar" in 31 -) 32 case "$OPTARG" in 33 help) 34 usage 35 exit 0 36 ;; 37 config-file=*) 38 config_file="$(readlink -f ${OPTARG#*=})" 39 ;; 40 spdk-abi-path=*) 41 user_abi_dir="$(readlink -f ${OPTARG#*=})" 42 ;; 43 release=*) 44 RELEASE="${OPTARG#*=}" 45 ;; 46 *) exit 1 ;; 47 esac 48 ;; 49 h) 50 usage 51 exit 0 52 ;; 53 c) config_file="$(readlink -f ${OPTARG#*=})" ;; 54 a) user_abi_dir="$(readlink -f ${OPTARG#*=})" ;; 55 *) exit 1 ;; 56 esac 57done 58 59source "$rootdir/test/common/autotest_common.sh" 60 61if [[ -e $config_file ]]; then 62 source "$config_file" 63fi 64 65source_abi_dir="${user_abi_dir:-"$testdir/abi"}" 66libdir="$rootdir/build/lib" 67libdeps_file="$rootdir/mk/spdk.lib_deps.mk" 68 69function check_header_filenames() { 70 local dups_found=0 71 72 include_headers=$(git ls-files -- $rootdir/include/spdk $rootdir/include/spdk_internal | xargs -n 1 basename) 73 dups= 74 for file in $include_headers; do 75 if [[ $(git ls-files "$rootdir/lib/**/$file" "$rootdir/module/**/$file" --error-unmatch 2> /dev/null) ]]; then 76 dups+=" $file" 77 dups_found=1 78 fi 79 done 80 81 if ((dups_found == 1)); then 82 echo "Private header file(s) found with same name as public header file." 83 echo "This is not allowed since it can confuse abidiff when determining if" 84 echo "data structure changes impact ABI." 85 echo $dups 86 return 1 87 fi 88} 89 90function get_release() { 91 local tag 92 93 tag=$(git describe --tags --abbrev=0 --exclude=LTS --exclude="*-pre" $1) 94 echo "${tag:0:6}" 95} 96 97function confirm_abi_deps() { 98 local processed_so=0 99 local abi_test_failed=false 100 local abidiff_output 101 local release 102 local suppression_file="$testdir/abigail_suppressions.ini" 103 104 release="${RELEASE:-$(get_release)}.x" 105 106 if [[ ! -d $source_abi_dir ]]; then 107 mkdir -p $source_abi_dir 108 echo "spdk-abi has not been found at $source_abi_dir, cloning" 109 git clone "https://github.com/spdk/spdk-abi.git" "$source_abi_dir" 110 fi 111 112 if [[ ! -d "$source_abi_dir/$release" ]]; then 113 echo "Release (${release%.*}) does not exist in spdk-abi repository" 114 return 1 115 fi 116 117 echo "* Running ${FUNCNAME[0]} against the ${release%.*} release" >&2 118 119 if ! hash abidiff; then 120 echo "Unable to check ABI compatibility. Please install abidiff." 121 return 1 122 fi 123 124 # Type suppression should be used for deliberate change in the structure, 125 # that do not affect the ABI compatibility. 126 # One example is utilizing a previously reserved field in the structure. 127 # Suppression file should contain at the very least the name of type 128 # and point to major SO_VER of the library it applies to. 129 # 130 # To avoid ignoring an actual changes in ABI for a particular SO_VER, 131 # the suppression should be as specific as possible to the change. 132 # has_data_member_* conditions can be used to narrow down the 133 # name of previously existing field, or specify the offset. 134 # Please refer to libabigail for details. 135 # 136 # Example format: 137 # [suppress_type] 138 # label = Added interrupt_mode field 139 # name = spdk_app_opts 140 # soname_regexp = ^libspdk_event\\.so\\.12\\.*$ 141 cat <<- EOF > ${suppression_file} 142 [suppress_type] 143 label = Removed spdk_nvme_accel_fn_table.submit_accel_crc32c field 144 name = spdk_nvme_accel_fn_table 145 soname_regexp = ^libspdk_nvme\\.so\\.14\\.*$ 146 has_data_member_regexp = ^submit_accel_crc32c$ 147 has_data_member_inserted_between = {64, 128} 148 EOF 149 150 for object in "$libdir"/libspdk_*.so; do 151 abidiff_output=0 152 153 so_file=$(basename $object) 154 if [ ! -f "$source_abi_dir/$release/$so_file" ]; then 155 echo "No corresponding object for $so_file in canonical directory. Skipping." 156 continue 157 fi 158 159 cmd_args=('abidiff' 160 $source_abi_dir/$release/$so_file "$libdir/$so_file" 161 '--headers-dir1' $source_abi_dir/$release/include 162 '--headers-dir2' $rootdir/include 163 '--leaf-changes-only' '--suppressions' $suppression_file) 164 165 if ! output=$("${cmd_args[@]}" --stat); then 166 # remove any filtered out variables. 167 output=$(sed "s/ [()][^)]*[)]//g" <<< "$output") 168 169 IFS="." read -r _ _ new_so_maj new_so_min < <(readlink "$libdir/$so_file") 170 IFS="." read -r _ _ old_so_maj old_so_min < <(readlink "$source_abi_dir/$release/$so_file") 171 172 found_abi_change=false 173 so_name_changed=no 174 175 if [[ $output == *"ELF SONAME changed"* ]]; then 176 so_name_changed=yes 177 fi 178 179 changed_leaf_types=0 180 if [[ $output =~ "leaf types summary: "([0-9]+) ]]; then 181 changed_leaf_types=${BASH_REMATCH[1]} 182 fi 183 184 removed_functions=0 changed_functions=0 added_functions=0 185 if [[ $output =~ "functions summary: "([0-9]+)" Removed, "([0-9]+)" Changed, "([0-9]+)" Added" ]]; then 186 removed_functions=${BASH_REMATCH[1]} changed_functions=${BASH_REMATCH[2]} added_functions=${BASH_REMATCH[3]} 187 fi 188 189 removed_vars=0 changed_vars=0 added_vars=0 190 if [[ $output =~ "variables summary: "([0-9]+)" Removed, "([0-9]+)" Changed, "([0-9]+)" Added" ]]; then 191 removed_vars=${BASH_REMATCH[1]} changed_vars=${BASH_REMATCH[2]} added_vars=${BASH_REMATCH[3]} 192 fi 193 194 if ((changed_leaf_types != 0)); then 195 if ((new_so_maj == old_so_maj)); then 196 abidiff_output=1 197 abi_test_failed=true 198 echo "Please update the major SO version for $so_file. A header accessible type has been modified since last release." 199 fi 200 found_abi_change=true 201 fi 202 203 if ((removed_functions != 0)) || ((removed_vars != 0)); then 204 if ((new_so_maj == old_so_maj)); then 205 abidiff_output=1 206 abi_test_failed=true 207 echo "Please update the major SO version for $so_file. API functions or variables have been removed since last release." 208 fi 209 found_abi_change=true 210 fi 211 212 if ((changed_functions != 0)) || ((changed_vars != 0)); then 213 if ((new_so_maj == old_so_maj)); then 214 abidiff_output=1 215 abi_test_failed=true 216 echo "Please update the major SO version for $so_file. API functions or variables have been changed since last release." 217 fi 218 found_abi_change=true 219 fi 220 221 if ((added_functions != 0)) || ((added_vars != 0)); then 222 if ((new_so_min == old_so_min && new_so_maj == old_so_maj)) && ! $found_abi_change; then 223 abidiff_output=1 224 abi_test_failed=true 225 echo "Please update the minor SO version for $so_file. API functions or variables have been added since last release." 226 fi 227 found_abi_change=true 228 fi 229 230 if [[ $so_name_changed == yes ]]; then 231 # All SO major versions are intentionally increased after LTS to allow SO minor changes during the supported period. 232 if [[ "$release" == "$(get_release LTS).x" ]]; then 233 found_abi_change=true 234 fi 235 if ! $found_abi_change; then 236 echo "SO name for $so_file changed without a change to abi. please revert that change." 237 abi_test_failed=true 238 fi 239 240 if ((new_so_maj != old_so_maj && new_so_min != 0)); then 241 echo "SO major version for $so_file was bumped. Please reset the minor version to 0." 242 abi_test_failed=true 243 fi 244 245 if ((new_so_min > old_so_min + 1)); then 246 echo "SO minor version for $so_file was incremented more than once. Please revert minor version to $((old_so_min + 1))." 247 abi_test_failed=true 248 fi 249 250 if ((new_so_maj > old_so_maj + 1)); then 251 echo "SO major version for $so_file was incremented more than once. Please revert major version to $((old_so_maj + 1))." 252 abi_test_failed=true 253 fi 254 fi 255 256 if ((abidiff_output == 1)); then 257 "${cmd_args[@]}" --impacted-interfaces || : 258 fi 259 fi 260 processed_so=$((processed_so + 1)) 261 done 262 rm -f $suppression_file 263 if [[ "$processed_so" -eq 0 ]]; then 264 echo "No shared objects were processed." 265 return 1 266 fi 267 echo "Processed $processed_so objects." 268 if [[ -z $user_abi_dir ]]; then 269 rm -rf "$source_abi_dir" 270 fi 271 if $abi_test_failed; then 272 echo "ERROR: ABI test failed" 273 exit 1 274 fi 275} 276 277list_deps_mk() { 278 local tab=$'\t' 279 280 make -f - <<- MAKE 281 SPDK_ROOT_DIR := $rootdir 282 include \$(SPDK_ROOT_DIR)/mk/spdk.common.mk 283 include \$(SPDK_ROOT_DIR)/mk/spdk.lib_deps.mk 284 285 all: \$(filter DEPDIRS-%,\$(.VARIABLES)) 286 287 # Ignore any event_* dependencies. Those are based on the subsystem 288 # configuration and not readelf 289 DEPDIRS-%: 290 $tab@echo "\$(@:DEPDIRS-%=%)=\"\$(filter-out event_%,\$(\$@))\"" 291 MAKE 292} 293 294function get_lib_shortname() { 295 local lib=${1##*/} 296 echo "${lib//@(libspdk_|.so)/}" 297} 298 299# shellcheck disable=SC2005 300sort_libs() { echo $(printf "%s\n" "$@" | sort); } 301 302function confirm_deps() { 303 local lib=$1 deplib lib_shortname symbols dep_names lib_make_deps found_symbol_lib 304 305 lib_shortname=$(get_lib_shortname "$lib") 306 lib_make_deps=(${!lib_shortname}) 307 308 symbols=($(readelf -s --wide "$lib" | awk '$7 == "UND" {print $8}' | sort -u)) 309 symbols_regx=$( 310 IFS="|" 311 echo "(${symbols[*]})" 312 ) 313 314 if ((${#symbols[@]} > 0)); then 315 for deplib in "$libdir/"libspdk_!("$lib_shortname").so; do 316 readelf -s --wide "$deplib" | grep -m1 -qE "DEFAULT\s+[0-9]+\s$symbols_regx$" || continue 317 found_symbol_lib=$(get_lib_shortname "$deplib") 318 # Ignore the env_dpdk readelf dependency. We don't want people explicitly linking against it. 319 if [[ $found_symbol_lib != *env_dpdk* ]]; then 320 dep_names+=("$found_symbol_lib") 321 fi 322 done 323 fi 324 325 diff=$(echo "${dep_names[@]}" "${lib_make_deps[@]}" | tr ' ' '\n' | sort | uniq -u) 326 if [ "$diff" != "" ]; then 327 touch $fail_file 328 cat <<- MSG 329 There was a dependency mismatch in the library $lib_shortname 330 The makefile (spdk.lib_deps.mk) lists: '$(sort_libs "${lib_make_deps[@]}")' 331 readelf outputs: '$(sort_libs "${dep_names[@]}")' 332 MSG 333 fi 334} 335 336function confirm_makefile_deps() { 337 echo "---------------------------------------------------------------------" 338 # Exclude libspdk_env_dpdk.so from the library list. We don't link against this one so that 339 # users can define their own environment abstraction. However we do want to still check it 340 # for dependencies to avoid printing out a bunch of confusing symbols under the missing 341 # symbols section. 342 SPDK_LIBS=("$libdir/"libspdk_!(env_dpdk).so) 343 fail_file="$testdir/check_so_deps_fail" 344 345 rm -f $fail_file 346 347 declare -A IGNORED_LIBS=() 348 if [[ $CONFIG_RDMA == n ]]; then 349 IGNORED_LIBS["rdma"]=1 350 fi 351 352 ( 353 source <(list_deps_mk) 354 for lib in "${SPDK_LIBS[@]}"; do confirm_deps "$lib" & done 355 wait 356 ) 357 358 if [ -f $fail_file ]; then 359 rm -f $fail_file 360 echo "ERROR: Makefile deps test failed" 361 exit 1 362 fi 363} 364 365if [[ -e $config_file ]]; then 366 config_params=$(get_config_params) 367 if [[ "$SPDK_TEST_OCF" -eq 1 ]]; then 368 config_params="$config_params --with-ocf=$rootdir/ocf.a" 369 fi 370 371 if [[ -f $rootdir/mk/config.mk ]]; then 372 $MAKE $MAKEFLAGS clean 373 fi 374 375 $rootdir/configure $config_params --with-shared 376 $MAKE $MAKEFLAGS 377fi 378 379xtrace_disable 380 381run_test "check_header_filenames" check_header_filenames 382run_test "confirm_abi_deps" confirm_abi_deps 383run_test "confirm_makefile_deps" confirm_makefile_deps 384 385if [[ -e $config_file ]]; then 386 $MAKE $MAKEFLAGS clean 387fi 388 389xtrace_restore 390