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 = Added disable_command_passthru field 144 name = spdk_nvmf_transport_opts 145 soname_regexp = ^libspdk_nvmf\\.so\\.18\\.*$ 146 has_data_member_regexp = ^reserved29$ 147 has_data_member_inserted_between = {232, 256} 148 [suppress_type] 149 label = Added opts.disable_command_passthru field 150 name = spdk_nvmf_transport 151 soname_regexp = ^libspdk_nvmf\\.so\\.18\\.*$ 152 [suppress_type] 153 label = Added disable_pcie_sgl_merge field 154 name = spdk_nvme_io_qpair_opts 155 soname_regexp = ^libspdk_nvme\\.so\\.13\\.*$ 156 has_data_member_regexp = ^reserved66$ 157 has_data_member_inserted_between = {528, end} 158 EOF 159 160 for object in "$libdir"/libspdk_*.so; do 161 abidiff_output=0 162 163 so_file=$(basename $object) 164 if [ ! -f "$source_abi_dir/$release/$so_file" ]; then 165 echo "No corresponding object for $so_file in canonical directory. Skipping." 166 continue 167 fi 168 169 cmd_args=('abidiff' 170 $source_abi_dir/$release/$so_file "$libdir/$so_file" 171 '--headers-dir1' $source_abi_dir/$release/include 172 '--headers-dir2' $rootdir/include 173 '--leaf-changes-only' '--suppressions' $suppression_file) 174 175 if ! output=$("${cmd_args[@]}" --stat); then 176 # remove any filtered out variables. 177 output=$(sed "s/ [()][^)]*[)]//g" <<< "$output") 178 179 IFS="." read -r _ _ new_so_maj new_so_min < <(readlink "$libdir/$so_file") 180 IFS="." read -r _ _ old_so_maj old_so_min < <(readlink "$source_abi_dir/$release/$so_file") 181 182 found_abi_change=false 183 so_name_changed=no 184 185 if [[ $output == *"ELF SONAME changed"* ]]; then 186 so_name_changed=yes 187 fi 188 189 changed_leaf_types=0 190 if [[ $output =~ "leaf types summary: "([0-9]+) ]]; then 191 changed_leaf_types=${BASH_REMATCH[1]} 192 fi 193 194 removed_functions=0 changed_functions=0 added_functions=0 195 if [[ $output =~ "functions summary: "([0-9]+)" Removed, "([0-9]+)" Changed, "([0-9]+)" Added" ]]; then 196 removed_functions=${BASH_REMATCH[1]} changed_functions=${BASH_REMATCH[2]} added_functions=${BASH_REMATCH[3]} 197 fi 198 199 removed_vars=0 changed_vars=0 added_vars=0 200 if [[ $output =~ "variables summary: "([0-9]+)" Removed, "([0-9]+)" Changed, "([0-9]+)" Added" ]]; then 201 removed_vars=${BASH_REMATCH[1]} changed_vars=${BASH_REMATCH[2]} added_vars=${BASH_REMATCH[3]} 202 fi 203 204 if ((changed_leaf_types != 0)); then 205 if ((new_so_maj == old_so_maj)); then 206 abidiff_output=1 207 abi_test_failed=true 208 echo "Please update the major SO version for $so_file. A header accessible type has been modified since last release." 209 fi 210 found_abi_change=true 211 fi 212 213 if ((removed_functions != 0)) || ((removed_vars != 0)); then 214 if ((new_so_maj == old_so_maj)); then 215 abidiff_output=1 216 abi_test_failed=true 217 echo "Please update the major SO version for $so_file. API functions or variables have been removed since last release." 218 fi 219 found_abi_change=true 220 fi 221 222 if ((changed_functions != 0)) || ((changed_vars != 0)); then 223 if ((new_so_maj == old_so_maj)); then 224 abidiff_output=1 225 abi_test_failed=true 226 echo "Please update the major SO version for $so_file. API functions or variables have been changed since last release." 227 fi 228 found_abi_change=true 229 fi 230 231 if ((added_functions != 0)) || ((added_vars != 0)); then 232 if ((new_so_min == old_so_min && new_so_maj == old_so_maj)) && ! $found_abi_change; then 233 abidiff_output=1 234 abi_test_failed=true 235 echo "Please update the minor SO version for $so_file. API functions or variables have been added since last release." 236 fi 237 found_abi_change=true 238 fi 239 240 if [[ $so_name_changed == yes ]]; then 241 # All SO major versions are intentionally increased after LTS to allow SO minor changes during the supported period. 242 if [[ "$release" == "$(get_release LTS).x" ]]; then 243 found_abi_change=true 244 fi 245 if ! $found_abi_change; then 246 echo "SO name for $so_file changed without a change to abi. please revert that change." 247 abi_test_failed=true 248 fi 249 250 if ((new_so_maj != old_so_maj && new_so_min != 0)); then 251 echo "SO major version for $so_file was bumped. Please reset the minor version to 0." 252 abi_test_failed=true 253 fi 254 255 if ((new_so_min > old_so_min + 1)); then 256 echo "SO minor version for $so_file was incremented more than once. Please revert minor version to $((old_so_min + 1))." 257 abi_test_failed=true 258 fi 259 260 if ((new_so_maj > old_so_maj + 1)); then 261 echo "SO major version for $so_file was incremented more than once. Please revert major version to $((old_so_maj + 1))." 262 abi_test_failed=true 263 fi 264 fi 265 266 if ((abidiff_output == 1)); then 267 "${cmd_args[@]}" --impacted-interfaces || : 268 fi 269 fi 270 processed_so=$((processed_so + 1)) 271 done 272 rm -f $suppression_file 273 if [[ "$processed_so" -eq 0 ]]; then 274 echo "No shared objects were processed." 275 return 1 276 fi 277 echo "Processed $processed_so objects." 278 if [[ -z $user_abi_dir ]]; then 279 rm -rf "$source_abi_dir" 280 fi 281 if $abi_test_failed; then 282 echo "ERROR: ABI test failed" 283 exit 1 284 fi 285} 286 287list_deps_mk() { 288 local tab=$'\t' 289 290 make -f - <<- MAKE 291 SPDK_ROOT_DIR := $rootdir 292 include \$(SPDK_ROOT_DIR)/mk/spdk.common.mk 293 include \$(SPDK_ROOT_DIR)/mk/spdk.lib_deps.mk 294 295 all: \$(filter DEPDIRS-%,\$(.VARIABLES)) 296 297 # Ignore any event_* dependencies. Those are based on the subsystem 298 # configuration and not readelf 299 DEPDIRS-%: 300 $tab@echo "\$(@:DEPDIRS-%=%)=\"\$(filter-out event_%,\$(\$@))\"" 301 MAKE 302} 303 304function get_lib_shortname() { 305 local lib=${1##*/} 306 echo "${lib//@(libspdk_|.so)/}" 307} 308 309# shellcheck disable=SC2005 310sort_libs() { echo $(printf "%s\n" "$@" | sort); } 311 312function confirm_deps() { 313 local lib=$1 deplib lib_shortname symbols dep_names lib_make_deps found_symbol_lib 314 315 lib_shortname=$(get_lib_shortname "$lib") 316 lib_make_deps=(${!lib_shortname}) 317 318 symbols=($(readelf -s --wide "$lib" | awk '$7 == "UND" {print $8}' | sort -u)) 319 symbols_regx=$( 320 IFS="|" 321 echo "(${symbols[*]})" 322 ) 323 324 if ((${#symbols[@]} > 0)); then 325 for deplib in "$libdir/"libspdk_!("$lib_shortname").so; do 326 readelf -s --wide "$deplib" | grep -m1 -qE "DEFAULT\s+[0-9]+\s$symbols_regx$" || continue 327 found_symbol_lib=$(get_lib_shortname "$deplib") 328 # Ignore the env_dpdk readelf dependency. We don't want people explicitly linking against it. 329 if [[ $found_symbol_lib != *env_dpdk* ]]; then 330 dep_names+=("$found_symbol_lib") 331 fi 332 done 333 fi 334 335 diff=$(echo "${dep_names[@]}" "${lib_make_deps[@]}" | tr ' ' '\n' | sort | uniq -u) 336 if [ "$diff" != "" ]; then 337 touch $fail_file 338 cat <<- MSG 339 There was a dependency mismatch in the library $lib_shortname 340 The makefile (spdk.lib_deps.mk) lists: '$(sort_libs "${lib_make_deps[@]}")' 341 readelf outputs: '$(sort_libs "${dep_names[@]}")' 342 MSG 343 fi 344} 345 346function confirm_makefile_deps() { 347 echo "---------------------------------------------------------------------" 348 # Exclude libspdk_env_dpdk.so from the library list. We don't link against this one so that 349 # users can define their own environment abstraction. However we do want to still check it 350 # for dependencies to avoid printing out a bunch of confusing symbols under the missing 351 # symbols section. 352 SPDK_LIBS=("$libdir/"libspdk_!(env_dpdk).so) 353 fail_file="$testdir/check_so_deps_fail" 354 355 rm -f $fail_file 356 357 declare -A IGNORED_LIBS=() 358 if grep -q 'CONFIG_RDMA?=n' $rootdir/mk/config.mk; then 359 IGNORED_LIBS["rdma"]=1 360 fi 361 362 ( 363 source <(list_deps_mk) 364 for lib in "${SPDK_LIBS[@]}"; do confirm_deps "$lib" & done 365 wait 366 ) 367 368 if [ -f $fail_file ]; then 369 rm -f $fail_file 370 echo "ERROR: Makefile deps test failed" 371 exit 1 372 fi 373} 374 375if [[ -e $config_file ]]; then 376 config_params=$(get_config_params) 377 if [[ "$SPDK_TEST_OCF" -eq 1 ]]; then 378 config_params="$config_params --with-ocf=$rootdir/ocf.a" 379 fi 380 381 if [[ -f $rootdir/mk/config.mk ]]; then 382 $MAKE $MAKEFLAGS clean 383 fi 384 385 $rootdir/configure $config_params --with-shared 386 $MAKE $MAKEFLAGS 387fi 388 389xtrace_disable 390 391run_test "check_header_filenames" check_header_filenames 392run_test "confirm_abi_deps" confirm_abi_deps 393run_test "confirm_makefile_deps" confirm_makefile_deps 394 395if [[ -e $config_file ]]; then 396 $MAKE $MAKEFLAGS clean 397fi 398 399xtrace_restore 400