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 version major minor patch suffix 92 93 if [[ -n "$1" ]]; then 94 version="$1" 95 else 96 IFS='.-' read -r major minor patch suffix < "$rootdir/VERSION" 97 version="v$major.$minor" 98 ((patch > 0)) && version+=".$patch" 99 version+=${suffix:+-$suffix} 100 fi 101 102 tag=$(git describe --tags --abbrev=0 --exclude=LTS --exclude="*-pre" "$version") 103 echo "${tag:0:6}" 104} 105 106function confirm_abi_deps() { 107 local processed_so=0 108 local abi_test_failed=false 109 local abidiff_output 110 local release 111 local suppression_file="$testdir/abigail_suppressions.ini" 112 113 release="${RELEASE:-$(get_release)}.x" 114 115 if [[ ! -d $source_abi_dir ]]; then 116 mkdir -p $source_abi_dir 117 echo "spdk-abi has not been found at $source_abi_dir, cloning" 118 git clone "https://github.com/spdk/spdk-abi.git" "$source_abi_dir" 119 fi 120 121 if [[ ! -d "$source_abi_dir/$release" ]]; then 122 echo "Release (${release%.*}) does not exist in spdk-abi repository" 123 return 1 124 fi 125 126 echo "* Running ${FUNCNAME[0]} against the ${release%.*} release" >&2 127 128 if ! hash abidiff; then 129 echo "Unable to check ABI compatibility. Please install abidiff." 130 return 1 131 fi 132 133 # Type suppression should be used for deliberate change in the structure, 134 # that do not affect the ABI compatibility. 135 # One example is utilizing a previously reserved field in the structure. 136 # Suppression file should contain at the very least the name of type 137 # and point to major SO_VER of the library it applies to. 138 # 139 # To avoid ignoring an actual changes in ABI for a particular SO_VER, 140 # the suppression should be as specific as possible to the change. 141 # has_data_member_* conditions can be used to narrow down the 142 # name of previously existing field, or specify the offset. 143 # Please refer to libabigail for details. 144 # 145 # Example format: 146 # [suppress_type] 147 # label = Added interrupt_mode field 148 # name = spdk_app_opts 149 # soname_regexp = ^libspdk_event\\.so\\.12\\.*$ 150 cat <<- EOF > ${suppression_file} 151 [suppress_type] 152 label = Removed spdk_nvme_accel_fn_table.submit_accel_crc32c field 153 name = spdk_nvme_accel_fn_table 154 soname_regexp = ^libspdk_nvme\\.so\\.14\\.*$ 155 has_data_member_regexp = ^submit_accel_crc32c$ 156 has_data_member_inserted_between = {64, 128} 157 EOF 158 159 for object in "$libdir"/libspdk_*.so; do 160 abidiff_output=0 161 162 so_file=$(basename $object) 163 if [ ! -f "$source_abi_dir/$release/$so_file" ]; then 164 echo "No corresponding object for $so_file in canonical directory. Skipping." 165 continue 166 fi 167 168 cmd_args=('abidiff' 169 $source_abi_dir/$release/$so_file "$libdir/$so_file" 170 '--headers-dir1' $source_abi_dir/$release/include 171 '--headers-dir2' $rootdir/include 172 '--leaf-changes-only' '--suppressions' $suppression_file) 173 174 if ! output=$("${cmd_args[@]}" --stat); then 175 # remove any filtered out variables. 176 output=$(sed "s/ [()][^)]*[)]//g" <<< "$output") 177 178 IFS="." read -r _ _ new_so_maj new_so_min < <(readlink "$libdir/$so_file") 179 IFS="." read -r _ _ old_so_maj old_so_min < <(readlink "$source_abi_dir/$release/$so_file") 180 181 found_abi_change=false 182 so_name_changed=no 183 184 if [[ $output == *"ELF SONAME changed"* ]]; then 185 so_name_changed=yes 186 fi 187 188 changed_leaf_types=0 189 if [[ $output =~ "leaf types summary: "([0-9]+) ]]; then 190 changed_leaf_types=${BASH_REMATCH[1]} 191 fi 192 193 removed_functions=0 changed_functions=0 added_functions=0 194 if [[ $output =~ "functions summary: "([0-9]+)" Removed, "([0-9]+)" Changed, "([0-9]+)" Added" ]]; then 195 removed_functions=${BASH_REMATCH[1]} changed_functions=${BASH_REMATCH[2]} added_functions=${BASH_REMATCH[3]} 196 fi 197 198 removed_vars=0 changed_vars=0 added_vars=0 199 if [[ $output =~ "variables summary: "([0-9]+)" Removed, "([0-9]+)" Changed, "([0-9]+)" Added" ]]; then 200 removed_vars=${BASH_REMATCH[1]} changed_vars=${BASH_REMATCH[2]} added_vars=${BASH_REMATCH[3]} 201 fi 202 203 if ((changed_leaf_types != 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. A header accessible type has been modified since last release." 208 fi 209 found_abi_change=true 210 fi 211 212 if ((removed_functions != 0)) || ((removed_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 removed since last release." 217 fi 218 found_abi_change=true 219 fi 220 221 if ((changed_functions != 0)) || ((changed_vars != 0)); then 222 if ((new_so_maj == old_so_maj)); then 223 abidiff_output=1 224 abi_test_failed=true 225 echo "Please update the major SO version for $so_file. API functions or variables have been changed since last release." 226 fi 227 found_abi_change=true 228 fi 229 230 if ((added_functions != 0)) || ((added_vars != 0)); then 231 if ((new_so_min == old_so_min && new_so_maj == old_so_maj)) && ! $found_abi_change; then 232 abidiff_output=1 233 abi_test_failed=true 234 echo "Please update the minor SO version for $so_file. API functions or variables have been added since last release." 235 fi 236 found_abi_change=true 237 fi 238 239 if [[ $so_name_changed == yes ]]; then 240 # All SO major versions are intentionally increased after LTS to allow SO minor changes during the supported period. 241 if [[ "$release" == "$(get_release LTS).x" ]]; then 242 found_abi_change=true 243 fi 244 if ! $found_abi_change; then 245 echo "SO name for $so_file changed without a change to abi. please revert that change." 246 abi_test_failed=true 247 fi 248 249 if ((new_so_maj != old_so_maj && new_so_min != 0)); then 250 echo "SO major version for $so_file was bumped. Please reset the minor version to 0." 251 abi_test_failed=true 252 fi 253 254 if ((new_so_min > old_so_min + 1)); then 255 echo "SO minor version for $so_file was incremented more than once. Please revert minor version to $((old_so_min + 1))." 256 abi_test_failed=true 257 fi 258 259 if ((new_so_maj > old_so_maj + 1)); then 260 echo "SO major version for $so_file was incremented more than once. Please revert major version to $((old_so_maj + 1))." 261 abi_test_failed=true 262 fi 263 fi 264 265 if ((abidiff_output == 1)); then 266 "${cmd_args[@]}" --impacted-interfaces || : 267 fi 268 fi 269 processed_so=$((processed_so + 1)) 270 done 271 rm -f $suppression_file 272 if [[ "$processed_so" -eq 0 ]]; then 273 echo "No shared objects were processed." 274 return 1 275 fi 276 echo "Processed $processed_so objects." 277 if [[ -z $user_abi_dir ]]; then 278 rm -rf "$source_abi_dir" 279 fi 280 if $abi_test_failed; then 281 echo "ERROR: ABI test failed" 282 exit 1 283 fi 284} 285 286list_deps_mk() { 287 local tab=$'\t' 288 289 make -f - <<- MAKE 290 SPDK_ROOT_DIR := $rootdir 291 include \$(SPDK_ROOT_DIR)/mk/spdk.common.mk 292 include \$(SPDK_ROOT_DIR)/mk/spdk.lib_deps.mk 293 294 all: \$(filter DEPDIRS-%,\$(.VARIABLES)) 295 296 # Ignore any event_* dependencies. Those are based on the subsystem 297 # configuration and not readelf 298 DEPDIRS-%: 299 $tab@echo "\$(@:DEPDIRS-%=%)=\"\$(filter-out event_%,\$(\$@))\"" 300 MAKE 301} 302 303function get_lib_shortname() { 304 local lib=${1##*/} 305 echo "${lib//@(libspdk_|.so)/}" 306} 307 308# shellcheck disable=SC2005 309sort_libs() { echo $(printf "%s\n" "$@" | sort); } 310 311function confirm_deps() { 312 local lib=$1 deplib lib_shortname symbols dep_names lib_make_deps found_symbol_lib 313 314 lib_shortname=$(get_lib_shortname "$lib") 315 lib_make_deps=(${!lib_shortname}) 316 317 symbols=($(readelf -s --wide "$lib" | awk '$7 == "UND" {print $8}' | sort -u)) 318 symbols_regx=$( 319 IFS="|" 320 echo "(${symbols[*]})" 321 ) 322 323 if ((${#symbols[@]} > 0)); then 324 for deplib in "$libdir/"libspdk_!("$lib_shortname").so; do 325 readelf -s --wide "$deplib" | grep -m1 -qE "DEFAULT\s+[0-9]+\s$symbols_regx$" || continue 326 found_symbol_lib=$(get_lib_shortname "$deplib") 327 # Ignore the env_dpdk readelf dependency. We don't want people explicitly linking against it. 328 if [[ $found_symbol_lib != *env_dpdk* ]]; then 329 dep_names+=("$found_symbol_lib") 330 fi 331 done 332 fi 333 334 diff=$(echo "${dep_names[@]}" "${lib_make_deps[@]}" | tr ' ' '\n' | sort | uniq -u) 335 if [ "$diff" != "" ]; then 336 touch $fail_file 337 cat <<- MSG 338 There was a dependency mismatch in the library $lib_shortname 339 The makefile (spdk.lib_deps.mk) lists: '$(sort_libs "${lib_make_deps[@]}")' 340 readelf outputs: '$(sort_libs "${dep_names[@]}")' 341 MSG 342 fi 343} 344 345function confirm_makefile_deps() { 346 echo "---------------------------------------------------------------------" 347 # Exclude libspdk_env_dpdk.so from the library list. We don't link against this one so that 348 # users can define their own environment abstraction. However we do want to still check it 349 # for dependencies to avoid printing out a bunch of confusing symbols under the missing 350 # symbols section. 351 SPDK_LIBS=("$libdir/"libspdk_!(env_dpdk).so) 352 fail_file="$testdir/check_so_deps_fail" 353 354 rm -f $fail_file 355 356 declare -A IGNORED_LIBS=() 357 if [[ $CONFIG_RDMA == n ]]; then 358 IGNORED_LIBS["rdma"]=1 359 fi 360 361 ( 362 source <(list_deps_mk) 363 for lib in "${SPDK_LIBS[@]}"; do confirm_deps "$lib" & done 364 wait 365 ) 366 367 if [ -f $fail_file ]; then 368 rm -f $fail_file 369 echo "ERROR: Makefile deps test failed" 370 exit 1 371 fi 372} 373 374if [[ -e $config_file ]]; then 375 config_params=$(get_config_params) 376 if [[ "$SPDK_TEST_OCF" -eq 1 ]]; then 377 config_params="$config_params --with-ocf=$rootdir/ocf.a" 378 fi 379 380 if [[ -f $rootdir/mk/config.mk ]]; then 381 $MAKE $MAKEFLAGS clean 382 fi 383 384 $rootdir/configure $config_params --with-shared 385 $MAKE $MAKEFLAGS 386fi 387 388xtrace_disable 389 390run_test "check_header_filenames" check_header_filenames 391run_test "confirm_abi_deps" confirm_abi_deps 392run_test "confirm_makefile_deps" confirm_makefile_deps 393 394if [[ -e $config_file ]]; then 395 $MAKE $MAKEFLAGS clean 396fi 397 398xtrace_restore 399